Click here to Skip to main content
15,896,336 members
Articles / Multimedia / DirectX

A DirectX Game: Quadrino

Rate me:
Please Sign up or sign in to vote.
4.89/5 (41 votes)
16 Oct 2008CPOL26 min read 397.8K   6.2K   125  
An interpretation of a popular falling block game implemented with DirectX that attempts to avoid any copyright infringement.
/* Tetrisview.cpp *************************************************************
Author:		Paul Watt, Copyright (c) 2002
Date:		4/2/2002
Purpose:	
******************************************************************************/
#include "stdafx.h"

#include "atlgdi.h"
#include "Tetrisview.h"
#include "DDUtil.h"

#include <limits>
#include <map>
using std::map;

typedef map<UINT, TetrisLine>	mapLines;
typedef mapLines::iterator		iterLines;

/* Public *********************************************************************
Author:		Paul Watt
Date:		4/2/2002
Purpose:	Constructor
******************************************************************************/
CTetrisView::CTetrisView ()
{
	m_pUpdateSurface= NULL;
	m_pBoard		= NULL;
	m_pBreman32		= NULL;

	m_pBackground = NULL;
	m_pTitle = NULL;
	m_pPause = NULL;
	m_pOptionScreen = NULL;
	m_pGameOver = NULL;
	m_pStartText = NULL;
	m_pOptionsText = NULL;
	m_pExitText = NULL;

	for (int index = 0; index < PT_COUNT+1; index++)
	{
		m_pPieceImages[index] = NULL;
	}

			//C: Initialize the update state for each of the display elements.
	m_pGame					= NULL;
	m_isBoardNeeded			= true;
	m_isBackgroundNeeded	= true;
	m_textDrawCount			= 0;

	m_isSwapWaiting			= false;
			//C: Initialize the descent rate to 2 rows / second.
	m_dDescentRate = 2.0 / 1000.0;
	m_dwStartTime = 0;
	m_fFPS	= 0.0f;

	m_state = NORMAL;
			//C: Initialize all of the rectangles to zero.
	::ZeroMemory(&m_rBoard, sizeof(RECT));
	::ZeroMemory(&m_rName, sizeof(RECT));
	::ZeroMemory(m_rPreview, sizeof(m_rPreview));
	::ZeroMemory(&m_rScore, sizeof(RECT));
	::ZeroMemory(&m_rStore, sizeof(RECT));
	::ZeroMemory(&m_rStart, sizeof(RECT));
			//C: Zero the sounds.
	m_pSoundBang	= NULL;
	m_pSoundDrop	= NULL;
	m_pSoundFade	= NULL;
	m_pSoundSetPiece= NULL;
	m_pSoundTumble	= NULL;
	m_pSoundGameOver= NULL;

}


/* Public *********************************************************************
Author:		Paul Watt
Date:		4/2/2002
Purpose:	Destructor
******************************************************************************/
CTetrisView::~CTetrisView()
{
			//C: Destroy the current objects.
	DestroyObjects();
			//C: Clear the animation objects.
	iterListAnimation iter = m_lstAnimations.begin();
	while (iter != m_lstAnimations.end())
	{
		delete *iter;
	}
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		5/4/2002
Purpose:	Destroys all of the objects that are related to the DirectDraw
			context.
Parameters:	NONE
Return:		HRESULT indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::DestroyObjects()
{
			//C: Free the images that have been created for the view.
	FreeImages();
			//C: Free the display surfaces.
	delete m_pUpdateSurface;
	m_pUpdateSurface = NULL;

	delete m_pBoard;
	m_pBoard = NULL;

	delete m_pBreman32;
	m_pBreman32 = NULL;
			//C: Release sound objects.
	delete m_pSoundBang;
	m_pSoundBang = NULL;
	delete m_pSoundDrop;
	m_pSoundDrop = NULL;
	delete m_pSoundFade;
	m_pSoundFade = NULL;
	delete m_pSoundSetPiece;
	m_pSoundSetPiece = NULL;
	delete m_pSoundTumble;
	m_pSoundTumble = NULL;
	delete m_pSoundGameOver;
	m_pSoundGameOver = NULL;

	m_textDrawCount	= 0;
			//C: Delete the objects of the DDrawWindow
	return DDrawWindow::DestroyObjects();
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/20/2002
Purpose:	Initializes this view to display a tetris game.  This function will
			initailize DirectDraw, prepare the display surfaces, and load 
			all of the images that are needed.
Parameters:	pSoundManager[in]: A pointer to the sound manager that will allow
				this view to create sounds to go along with the animations.
Return:		HRESULT that indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::Init ()
{
	HRESULT hr;
			//C: Verify that the primary surface in DDrawWindow was created properly.
	if (!GetFrontBuffer())
	{
#pragma message( REMIND( "PMW: Check if there is an uninitialized HRESULT." ) )
		return E_POINTER;
	}
			//C: Call init on the base class.
	DDrawWindow::Init();
			//C: Create the update surface.
	if (IsWindowed())
	{
		RECT rClient;
		GetClientRect(&rClient);
		DWORD dwWidth = rClient.right - rClient.left;
		DWORD dwHeight= rClient.bottom - rClient.top;

		m_pUpdateSurface = ::CreateOffScreenSurface(m_pDD, dwWidth, dwHeight);
	}
	else
	{
		m_pUpdateSurface = ::CreateOffScreenSurface(m_pDD, m_pSurface->GetWidth(), m_pSurface->GetHeight());
	}
			//C: Create a surface for the board display.
	m_pBoard = ::CreateOffScreenSurface(m_pDD, BOARD_WIDTH * UNIT_SIZE, BOARD_HEIGHT * UNIT_SIZE);
	if (NULL == m_pBoard)
	{
		return E_FAIL;
	}
	m_pBoard->SetSourceColorKey(0x00000000);

	m_isBoardNeeded = true;
	m_isBackgroundNeeded = true;
			//C: Force all of the display elements to repaint.
	Flush();
			//C: Generate the path to the layout file for the current level.
	TCHAR szPath[_MAX_PATH];
	if (0 == ::GetModuleFileName(NULL, szPath, _MAX_PATH))
	{
		return E_FAIL;
	}

	::PathRemoveFileSpec(szPath);
	_tcscat(szPath, "\\layout.ini");
			//C: Load the layout required for the current level.
	hr = LoadLayout(szPath);
	if (FAILED(hr))
	{
		return hr;
	}
			//C: Initialize the board surface properly.
	if (m_pGame)
	{
		UpdateBoard(true);
	}
	else
	{
		m_pBoard->FillColor(NULL, 0x00000000);
	}
			//C: Create all of the fonts that are required.
			//C: Create a Breamn 32 point font
	LOGFONT lf;
	lf.lfHeight			= 32;
	lf.lfWidth			= 0;
	lf.lfEscapement		= 0;
	lf.lfOrientation    = 0;
	lf.lfWeight         = FW_BOLD;
	lf.lfItalic         = FALSE;
	lf.lfUnderline      = FALSE;
	lf.lfStrikeOut      = FALSE;
	lf.lfCharSet        = ANSI_CHARSET;
	lf.lfOutPrecision   = OUT_TT_PRECIS;
	lf.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
	lf.lfQuality        = DEFAULT_QUALITY;
	lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
	_tcsncpy(lf.lfFaceName, "Breman", LF_FACESIZE-1);

	m_pBreman32 = CreateFontSurface(m_pDD, lf, ' ', 'Z', RGB(0x31,0x04,0x6A));
			//C: Initialize sounds.
#pragma message( REMIND( "PMW: Revist the path specification" ) )
	m_SoundManager.Initialize(m_hWnd);

	char szDir[_MAX_PATH];
	char szSound[_MAX_PATH];
		//C: Get the directory where the current program is running.
	if (0 == ::GetModuleFileName(NULL, szDir, _MAX_PATH))
	{
		::MessageBox(m_hWnd, "Could not identify application resources.  Tetris will now exit.", "Error", MB_OK | MB_ICONSTOP);
		return -1;
	}

	char *szFileName = ::strrchr(szDir, '\\');
	if (szFileName)
	{
		*szFileName = NULL;
	}

	::strcpy(szSound, szDir);
	::strcat(szSound, "\\Sounds\\bang.wav");
	m_SoundManager.CreateSound(&m_pSoundBang, szSound, 0, 2);

	::strcpy(szSound, szDir);
	::strcat(szSound, "\\Sounds\\drops.wav");
	m_SoundManager.CreateSound(&m_pSoundDrop, szSound, 0, 10);

	::strcpy(szSound, szDir);
	::strcat(szSound, "\\Sounds\\fade.wav");
	m_SoundManager.CreateSound(&m_pSoundFade, szSound);

	::strcpy(szSound, szDir);
	::strcat(szSound, "\\Sounds\\SetPiece.wav");
	m_SoundManager.CreateSound(&m_pSoundSetPiece, szSound, 0, 3);

	::strcpy(szSound, szDir);
	::strcat(szSound, "\\Sounds\\tumble.wav");
	m_SoundManager.CreateSound(&m_pSoundTumble, szSound, 0, 4);

	::strcpy(szSound, szDir);
	::strcat(szSound, "\\Sounds\\gameover.wav");
	m_SoundManager.CreateSound(&m_pSoundGameOver, szSound, 0, 4);
			//C: Success.
	return S_OK;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/20/2002
Purpose:	Changes the style of the windows when the screen mode changes in
			order to properly handle messages.
Parameters:	NONE
Return:		HRESULT that indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::AdjustWindowForModeChange ()
{
	HWND hWndParent = GetParent();

	LONG lStyle   = ::GetWindowLong(hWndParent, GWL_STYLE);
	LONG lStyleEx = ::GetWindowLong(hWndParent, GWL_EXSTYLE);
	if (IsWindowed())
	{
		::SetWindowLong(hWndParent, GWL_STYLE, lStyle | WS_CAPTION);

		RECT rMain = {0, 0, 800, 600};
		::AdjustWindowRectEx(&rMain, WS_BORDER | WS_OVERLAPPED | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU, FALSE, 0);
		::OffsetRect(&rMain, -rMain.left, -rMain.top);

		::SetWindowPos(hWndParent, NULL, 0,0,rMain.right,rMain.bottom, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
	}
	else
	{
		::SetWindowLong(hWndParent, GWL_STYLE, lStyle & ~WS_CAPTION);
	}

	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		4/5/2002
Purpose:	Sets the game object for this view.
Parameters:	pGame[in]: A pointer to the Game object to use for this view.
Return:		HRESULT returns the status of this function.
******************************************************************************/
HRESULT CTetrisView::SetGame (CTetrisGame *pGame)
{
			//C: Test if the game has already been set.
	if (NULL != m_pGame)
	{
		return E_FAIL;
	}
			//C: Set the game object.
	m_pGame = pGame;
			//C: Set the current descent rate for the game.
	m_pGame->SetDescentRate(m_dDescentRate);
			//C: Success;
	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		4/20/2002
Purpose:	Flushes all of the state that is associated with display parameters.
Parameters:	NONE
Return:		-
******************************************************************************/
void CTetrisView::Flush()
{
	m_Store			= PT_EMPTY;
	m_Preview[0]	= PT_EMPTY;
	m_Preview[1]	= PT_EMPTY;
	m_Preview[2]	= PT_EMPTY;

	m_dwScore		= 0xFFFFFFFF;

	m_isBackgroundNeeded = true;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/7/2002
Purpose:	Loads all of the images that are needed to display the game.
Parameters:	NONE
Return:		HRESULT indicates the status of the function.
******************************************************************************/
HRESULT CTetrisView::LoadLayout(LPCTSTR pszLayout)
{
			//C: Verify that this view has ben initialized.  The direct draw 
			//   context pointer is required for this function.
	if (NULL == m_pDD)
	{
		return ERROR_INVALID_DATA;
	}
			//C: Verify input parameters.
	if (!pszLayout)
	{
		return E_INVALIDARG;
	}
			//C: Create an array of settings to make loading all 
			//   of the settings easier from a loop.
	DDSurface **surfaces[15];
	surfaces[0] = &m_pTitle;
	surfaces[1] = &m_pPause;
	surfaces[2] = &m_pOptionScreen;
	surfaces[3] = &m_pGameOver;
	surfaces[4] = &m_pBackground;
	surfaces[5] = &m_pStartText;
	surfaces[6] = &m_pOptionsText;
	surfaces[7] = &m_pExitText;
	surfaces[8] = &m_pPieceImages[PT_LINE];
	surfaces[9] = &m_pPieceImages[PT_LEFT];
	surfaces[10] = &m_pPieceImages[PT_RIGHT];
	surfaces[11] = &m_pPieceImages[PT_T];
	surfaces[12] = &m_pPieceImages[PT_BLOCK];
	surfaces[13] = &m_pPieceImages[PT_Z];
	surfaces[14] = &m_pPieceImages[PT_S];

	TCHAR	*szImageKeys[15] = {
									"title",
									"pause",
									"optionscreen",
									"gameover",
									"background",
									"start",
									"options",
									"exit",
									"line",
									"left",
									"right",
									"t",
									"block",
									"z",
									"s"
								};
			//C: Create a string that contains the directory where the images will
			//   be stored.
	TCHAR szDirectory[_MAX_PATH];
	TCHAR szPath[_MAX_PATH];
	TCHAR szTemp[_MAX_PATH];
	if (0 == ::GetModuleFileName(NULL, szDirectory, _MAX_PATH))
	{
		return E_FAIL;
	}

	::PathRemoveFileSpec(szDirectory);
	_tcscat(szDirectory, "\\");
			//C: Read the settings from the ini file.
	TCHAR szEmpty[] = _T("");
	TCHAR szSetting[_MAX_PATH];
			//C: Load all of the images.
	int index;
	for (index = 0; index < 15; ++index)
	{
			//C: Load the setting from the file.
		if (!GetPrivateProfileString(_T("images"), szImageKeys[index], szEmpty, szSetting, _MAX_PATH, pszLayout))
		{
			return E_FAIL;
		}
		
		_tcscpy(szTemp, szDirectory);
		_tcscat(szTemp, szSetting);
		if (!PathCanonicalize(szPath, szTemp))
		{
			return E_FAIL;
		}
		
		*surfaces[index] = ::CreateOffScreenSurface(m_pDD, szPath);
		if (!*surfaces[index])
		{
			return E_FAIL;
		}
	}
			//C: Set the color keys for certain images.
	m_pPause->SetSourceColorKey(0x0000FF00);
	m_pGameOver->SetSourceColorKey(0x0000FF00);
	m_pStartText->SetSourceColorKey(0x0000FF00);
	m_pOptionsText->SetSourceColorKey(0x0000FF00);
	m_pExitText->SetSourceColorKey(0x0000FF00);
			//C: Load all of the board settings for this level.
	RECT *rects[7];
	rects[0] = &m_rBoard;
	rects[1] = &m_rName;
	rects[2] = &m_rPreview[0];
	rects[3] = &m_rPreview[1];
	rects[4] = &m_rPreview[2];
	rects[5] = &m_rScore;
	rects[6] = &m_rStore;

	TCHAR	*szRectSects[7] =	{
									"board",
									"name",
									"preview1",
									"preview2",
									"preview3",
									"score",
									"store"
								};
			//C: Read the settings for the rectagle positions.
	for (index = 0; index < 7; ++index)
	{
			//C: Load the setting from the file.
		rects[index]->left	= GetPrivateProfileInt(szRectSects[index], _T("x"), -1, pszLayout);
		rects[index]->top	= GetPrivateProfileInt(szRectSects[index], _T("y"), -1, pszLayout);
		rects[index]->right	= GetPrivateProfileInt(szRectSects[index], _T("width"), -1, pszLayout);
		rects[index]->bottom	= GetPrivateProfileInt(szRectSects[index], _T("height"), -1, pszLayout);

			//C: Verify the data from all four parameters.
		if (-1 == rects[index]->left	||
			-1 == rects[index]->top		||
			-1 == rects[index]->right	||
			-1 == rects[index]->bottom	
			)
		{
			return E_FAIL;
		}
			//C: Adjust the right and bottom parameters.
		rects[index]->right  += rects[index]->left;
		rects[index]->bottom += rects[index]->top;
	}
			//C: Calculate the start postion based on the position of the board.
	UINT nBoardWidth = m_rBoard.right - m_rBoard.left;
	m_rStart.left	= m_rBoard.left + (nBoardWidth / 2) - (3 * UNIT_SIZE);
	m_rStart.top	= 0;
	m_rStart.right	= m_rStart.left + BLOCK_SIZE;
	m_rStart.bottom	= m_rStart.top + BLOCK_SIZE;
			//C: Done.
	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		4/17/2002
Purpose:	Free all of the images that have been created for this view.
Parameters:	NONE
Return:		HRESULT indicating the status of this function.
******************************************************************************/
HRESULT CTetrisView::FreeImages()
{
	delete m_pBackground;
	delete m_pTitle;
	delete m_pPause;
	delete m_pOptionScreen;
	delete m_pGameOver;
	delete m_pStartText;
	delete m_pOptionsText;
	delete m_pExitText;
	delete m_pPieceImages[PT_LINE];
	delete m_pPieceImages[PT_LEFT];
	delete m_pPieceImages[PT_RIGHT];
	delete m_pPieceImages[PT_T];
	delete m_pPieceImages[PT_BLOCK];
	delete m_pPieceImages[PT_Z];
	delete m_pPieceImages[PT_S];

	return S_OK;
}

/* Public *********************************************************************
Author:		Paul Watt
Date:		4/5/2002
Purpose:	Updates the current display frame for the game.
Parameters: NONE
Return:		HRESULT returns the status of this function call.
******************************************************************************/
HRESULT CTetrisView::UpdateFrame()
{
	HRESULT hResult;
			//C: Insure that the update buffer has been properly initialized.
			//   If not, then Exit.
	if (!m_pUpdateSurface)
	{
		return E_FAIL;
	}
			//C: Create a rect that is the size of the update buffer.
	RECT rClient;
	rClient.left   = 0;
	rClient.top    = 0;
	rClient.right  = m_pUpdateSurface->GetWidth();
	rClient.bottom = m_pUpdateSurface->GetHeight();
			//C: The current mode of the game will determine what is drawn.
	if (false == m_pGame->IsInit())
	{
		if (m_textDrawCount > 2)
		{
			return S_OK;
		}

		if (OPTIONS == m_state)
		{
			//C: Draw the Options screen.
			m_pBackSurface->BltFast(0, 0, m_pOptionScreen, &rClient, DDBLTFAST_NOCOLORKEY);
		}
		else if (GAMEOVER == m_state)
		{
			//C: Draw the GameOver screen.
			DrawGameOver(m_pUpdateSurface);
			m_pBackSurface->BltFast(0, 0, m_pUpdateSurface, &rClient, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
		}
		else
		{
			//C: Draw the main screen.
			DrawMain(m_pUpdateSurface);
			m_pBackSurface->BltFast(0, 0, m_pUpdateSurface, &rClient, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
		}

		m_textDrawCount++;
		return Flip();
	}
	else if (true == m_pGame->IsPaused())
	{
		if (m_textDrawCount > 2)
		{
			return S_OK;
		}
			//C: Draw the pause screen.
		hResult = DrawPause(m_pUpdateSurface);
		if (FAILED(hResult))
		{
			return hResult;
		}

		m_textDrawCount++;
		m_pBackSurface->BltFast(0, 0, m_pUpdateSurface, &rClient, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
		return Flip();
	}
	m_textDrawCount = 0;
			//C: Verify and update the background image.
	hResult = DrawBackground(m_pUpdateSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Verify and update the store piece.
	hResult = DrawStore(m_pUpdateSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Verify and update the three preview pieces.
	hResult = DrawPreview(m_pUpdateSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Verify and update the score.
	hResult = DrawScore(m_pUpdateSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Verify and update the game board.
	POINT ptBoardOffset = {m_rBoard.left, m_rBoard.top};
	UpdateBoard(false);
	hResult = DrawBoard(m_pUpdateSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Blt the update buffer, to the backbuffer.
	m_pBackSurface->BltFast(0, 0, m_pUpdateSurface, &rClient, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
			//C: Perform all of the animations.
	Animate(m_pBackSurface);
			//C: Get the new position of the piece.
	double dX = m_pGame->GetPiecePosX();
	double dY = m_pGame->GetPiecePosY();
			//C: Determine where the shadow should be placed.
	int iTargetY = m_pGame->GetTargetPos();			
			//C: If the set timer has been started, see if the time has
			//   expired and set the piece.
	if (0 != m_dwStartTime)
	{
		DWORD dwCurTime = ::GetTickCount();
			//C: Check if the user was able to move their piece to the side
			//   and it is able to drop again.
		if (dY < iTargetY)
		{
			//C: Clear the timer.
			m_dwStartTime	= 0;
			dwCurTime		= 0;
			//C: Enable the descent of the game again.
			TriggerEvents(START_DESCENT);
		}
			//C: Calculate the position where the piece should be drawn.
		RECT rPos;

		rPos.left	= dX * UNIT_SIZE + m_rBoard.left;
		rPos.top	= iTargetY * UNIT_SIZE + m_rBoard.top;
		rPos.right	= rPos.left + BLOCK_SIZE;
		rPos.bottom	= rPos.top + BLOCK_SIZE;
			
		if (dwCurTime - m_dwStartTime > SET_TIME)
		{
			hResult = OnSetPiece (rPos);
			if (FAILED(hResult))
			{
			//C: The only collision that should be detected is the top border.
				ATLASSERT(TETRIS_E_COLLISION_TOP == hResult);
				OnGameOver ();
			}
		}
		else
		{
			//C: The timer has not yet expired, paint the piece on the board.
			PIECE_TYPE piece = m_pGame->GetActivePiece();
			DrawPiece	(
						m_pBackSurface, 
						m_pPieceImages[piece],
						piece, 
						m_pGame->GetPieceOrientation(), 
						rPos, 
						DP_LEFT | DP_TOP
						);
		}
	}
	else
	{
			//C: Only paint the piece if the active piece is currently descending.
		if (m_pGame->IsDescending())
		{
			//C: If the piece position is passed the shadow position, 
			if (dY >= iTargetY)
			{
			//C: Halt the descent of the active piece.
				m_pGame->StopDescent();
			//C: Reset the timer for setting the piece.			
				m_dwStartTime = ::GetTickCount();
			//C: Set the current piece pos y to the same as the target pos y.
				dY = iTargetY;
			//C: Play the Sound.
				if (m_pSoundSetPiece)
				{
					m_pSoundSetPiece->Play();
				}
			}

			//C: Initialize the painting positions.
			RECT rPos;

			rPos.left	= dX * UNIT_SIZE + m_rBoard.left;
			rPos.top	= iTargetY * UNIT_SIZE + m_rBoard.top;
			rPos.right	= rPos.left + BLOCK_SIZE;
			rPos.bottom	= rPos.top + BLOCK_SIZE;
			//C: Paint the shadow piece.
			PIECE_TYPE piece = m_pGame->GetActivePiece();
			DrawPiece	(
						m_pBackSurface, 
						m_pPieceImages[piece],
						piece, 
						m_pGame->GetPieceOrientation(), 
						rPos, 
						DP_LEFT | DP_TOP | DP_SHADOW
						);

			//C: Paint the piece on the backbuffer.
			rPos.top	= dY * UNIT_SIZE + m_rBoard.top;
			rPos.bottom	= rPos.top + BLOCK_SIZE;
				
			DrawPiece	(
						m_pBackSurface, 
						m_pPieceImages[piece],
						piece, 
						m_pGame->GetPieceOrientation(), 
						rPos, 
						DP_LEFT | DP_TOP
						);
		}
	}

#ifdef _DEBUG
			//C: Track the frame rate of this view.
	static FLOAT fLastTime = 0.0f;
	static DWORD dwFrames  = 0L;
	static TCHAR szFPS[32];

	FLOAT fTime = DDTimer(TIMER_GETABSOLUTETIME);
    ++dwFrames;
	        //C: Update the scene stats once per second
    if( fTime - fLastTime > 1.0f )
    {
        m_fFPS    = dwFrames / (fTime - fLastTime);
        fLastTime = fTime;
        dwFrames  = 0L;

		_stprintf( szFPS, _T("%.02f fps (%dx%d)"), m_fFPS, m_pBackSurface->GetWidth(), m_pBackSurface->GetHeight());
	}

	m_pBreman32->TextOut(m_pBackSurface, 10, 10, szFPS, -1);
			//C: Show the descent rate as well.
	TCHAR szDescentRate[32];
	_stprintf( szDescentRate, _T("%.02f Lines per second"), m_dDescentRate * 1000);
	m_pBreman32->TextOut(m_pBackSurface, 10, 40, szDescentRate, -1);
#endif
			//C: Flip the pages.
	return Flip();
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		4/17/2002
Purpose:	Allows the control of this view to activate an animation that is
			developed into this view.
Parameters:	pSurface[in]: The surface to which the animations will be drawn.
Return:		-
******************************************************************************/
HRESULT CTetrisView::Animate(DDSurface *pSurface)
{
			//C: Check input parameters.
	if (!pSurface)
	{
		return E_INVALIDARG;
	}
			//C: Cycle through the entire list of animations rendering the
			//   current frame for each animation.
	iterListAnimation iter = m_lstAnimations.begin();
	while (iter != m_lstAnimations.end())
	{
			//C: Get the current time.
		FLOAT fCurTime = DDTimer(TIMER_GETABSOLUTETIME);
			//C: Render the frame for this animation.
		HRESULT hResult;
		hResult = (*iter)->RenderFrame(pSurface, fCurTime);
		if (FAILED(hResult))
		{
			return hResult;
		}
			//C: If the animation is complete, remove it from the list.
		if ((*iter)->IsComplete())
		{
			//C: Trigger any events that the animation may have held.
			TriggerEvents((*iter)->GetTriggerID());
			//C: Remove the animation from the list.
			delete *iter;
			iter = m_lstAnimations.erase(iter);
		}
		else
		{
			//C: Otherwise increment to the next animation object.
			iter++;
		}			
	}

	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		5/24/2002
Purpose:	Triggers one of a small set of predefined events.
Parameters:	iEventID[in]: The ID of the event to trigger.
Return:		HRESULT indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::TriggerEvents(DWORD dwEventID)
{
	HRESULT hResult		= S_OK;

	if (REFRESH_PREVIEW1 & dwEventID)
	{
		m_Preview[0] = PT_EMPTY;
	}

	if (REFRESH_PREVIEW2 & dwEventID)
	{
		m_Preview[1] = PT_EMPTY;

	}

	if (REFRESH_PREVIEW3 & dwEventID)
	{
		m_Preview[2] = PT_EMPTY;
	}

	if (REFRESH_STORE & dwEventID)
	{
		m_Store = PT_EMPTY;
	}

	if (REFRESH_BOARD & dwEventID)
	{
		POINT ptBoardOffset = {m_rBoard.left, m_rBoard.top};
		UpdateBoard(true);
		DrawBoard(m_pUpdateSurface);
	}

	if (START_DESCENT & dwEventID)
	{
		m_pGame->StartDescent();
			//C: If a swap is waiting, swap the current pieces.
		if (m_isSwapWaiting)
		{
			OnSwap();
		}
	}

	if (STOP_DESCENT & dwEventID)
	{
		m_pGame->StopDescent();
	}

	return hResult;
}

/* Private ********************************************************************
Author:		Paul Watt
Date:		4/2/2002
Purpose:	Paint handler for this window.  All that it will simply do is blt
			the back buffer to the main window.  This is only done in windowed
			mode.  In full screen mode, the page flipper will automatically
			update the window.
Parameters:	hdc[in]: A pre-initailized dc to use.  In most cases however, this
				variable will be NULL.
Return:		0
******************************************************************************/
LRESULT CTetrisView::OnPaint(HDC hdc)
{
			//C: Validate the surface to prevent other paint messages from occuring.
	RedrawWindow(NULL, NULL, RDW_VALIDATE | RDW_NOERASE | RDW_NOFRAME);
			//C: If the window is currently in FullScreen mode, then exit.
	if (false == IsWindowed())
	{
		return 0;
	}
			//C: Verify that the primary and backbuffer surfaces are present.
	if (NULL == m_pSurface || NULL == m_pBackSurface)
	{
		return 0;
	}
			//C: Blt the Back surface to the primary surface.
	RECT rClient;
	GetClientRect(&rClient);

	DDBLTFX fx;
	fx.dwSize = sizeof(fx);
	fx.dwROP  = SRCCOPY;

	m_pSurface->Blt(&rClient, m_pBackSurface, NULL, DDBLT_ROP | DDBLT_WAIT, &fx);

	return 0;
}


/* Private ********************************************************************
Author:		Paul Watt
Date:		4/2/2002
Purpose:	Handles the size message.  This message should only occur when the
			window is first created, but it is important to allow the backbuffer,
			and the update surface to be created properly.
Parameters:	nMode[in]: Indicates how the window is sized.
			size[in]: Indicates the width and height of the client area of 
				the window.
Return:		Always returns 0.	
******************************************************************************/
LRESULT CTetrisView::OnSize(UINT nMode, CSize size)
{
			//C: If the resizeing flag is not restored or maximized then do not do
			//   anything.
	if (SIZE_MAXHIDE == nMode || SIZE_MINIMIZED == nMode)
	{
		SetMsgHandled(FALSE);
		return 0;
	}
			//C: If the current mode is windowed mode, then recreate the
			//   back surface to match the new size of this window.  
	if (IsWindowed())
	{	
			//C: Create a backbuffer surface to match teh size of the window.
		delete m_pBackSurface;
		m_pBackSurface = CreateOffScreenSurface(m_pDD, size.cx, size.cy);
			//C: Initalize the update surface.
		if (m_pBackSurface)
		{
			m_pBackSurface->FillColor(NULL, GetBackgroundColor());
		}
			//C: Create the update surface for the full screen mode.
		delete m_pUpdateSurface;
		m_pUpdateSurface = ::CreateOffScreenSurface(m_pDD, size.cx, size.cy);
			//C: Initalize the update surface.
		if (m_pUpdateSurface)
		{
			m_pUpdateSurface->FillColor(NULL, GetBackgroundColor());
		}

		m_isBackgroundNeeded = true;
	}

	return 0;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		5/15/2002
Purpose:	Handler that will clear the complete lines for the game board.  This
			function will start the animations and clear the pieces in the game
			board.
Parameters:	rPos[in]:  The position where the piece should be drawn.	
Return:		HRESULT indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::OnSetPiece	(RECT &rPos)
{
	PIECE_TYPE	piece	= m_pGame->GetActivePiece();
	UINT		nDir	= m_pGame->GetPieceOrientation();
			//C: Set the game piece on the board.
	HRESULT hResult = m_pGame->SetPiece();
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Update the board display so that the new piece will be reflected
			//   in the painting of the pieces.
	UpdateBoard(false);
			//C: Update the board and flip the changes tot he screen.
	DrawBoard(m_pUpdateSurface);
	Flip();
	m_isBoardNeeded = true;
			//C: The piece was successfully set into the board.
			//C: Clear the start timer.
	m_dwStartTime = 0;
			//C: Look at all of the lines.  Create a list of lines that are
			//   completed normally, and the lines that contain blocks.
	setUINT		sNormal;
	setUINT		sBlock;
	TetrisLine	line;
	for (UINT index = 0; index < BOARD_HEIGHT; ++index)
	{
		if (m_pGame->IsLineComplete(index))
		{
			//C: Get the line data in order to determine if it contains a 
			//   completed Super Block.
			m_pGame->GetLine(index, line);
			int element;
			for (element = 0; element < BOARD_WIDTH; ++element)
			{
				if (PT_GOLD == line.nType[element] || PT_SILVER == line.nType[element])
				{
					sBlock.insert(index);
					break;
				}
			}
			//C: If the element = BoardWidth, then there were no super blocks
			//   in the line, and the piece is a normal line.
			if (BOARD_WIDTH == element)
			{
				sNormal.insert(index);
			}
		}
	}
			//C: Paint the set animation.
#pragma message( REMIND( "PMW: Revisit this." ) )
//	CreateSetPiece(g_Pieces[piece][nDir], rPos, !sBlock.empty());
			//C: Make a cache of the current board.
	int BoardWidth  = BOARD_WIDTH * UNIT_SIZE;
	int BoardHeight = BOARD_HEIGHT * UNIT_SIZE;

	DDOffScreenSurface *pBoard = ::CreateOffScreenSurface(m_pDD, BoardWidth, BoardHeight);
	RECT rBounds = {0,0,BoardWidth, BoardHeight};
	pBoard->BltFast(0,0,m_pBoard, &rBounds, 0);
			//C: Animate the super block pieces fading away.
	FLOAT fDelay = 0.0;
	if (S_OK == CreateLineFade(sBlock))
	{
		fDelay = 1.0f;
	}
			//C: Start the animation for the Normal pieces being removed.
	CreateLineExplosion(sNormal, pBoard, fDelay);
			//C: Clear the lines from the game.
	m_pGame->ClearCompletedLines();
			//C: Create a new piece.
	m_pGame->NewPiece();
			//C: Animate the new piece coming into play.
	NewPieceAnimation(300, fDelay);
			//C: Set the current rate based on the number of pieces that the user
			//   has played.
	DWORD piecesPlayed		= m_pGame->GetPiecesPlayedCount();
	DWORD segmentsPlayed	= piecesPlayed / 25;
			//C: For every segment, add 1/4 of a line increase in descent speed.
			//C: The maximum rate will be 20 lines / second.
	DOUBLE newDescent = min((2.0 + (segmentsPlayed * 0.25)) / 1000.0, MAX_DESCENT);
			//C: If this new descent rate is greater than the previous, then 
			//   update the descent rate.
	if (m_dDescentRate < newDescent)
	{
		m_dDescentRate = newDescent;
		m_pGame->SetDescentRate(m_dDescentRate);
	}
			//C: Delete the cache that was created of the board.
	delete pBoard;

	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		5/15/2002
Purpose:	Event handler for when the active piece is swapped with the store 
			piece.
Parameters:	NONE	
Return:		HRESULT indicates the status of this function.  If the piece can
			be successfully swapped, then S_OK will be returned, otherwise
			S_FALSE.
******************************************************************************/
HRESULT CTetrisView::OnSwap ()
{
			//C: Test if it is possible to swap the store piece.
	if (m_pGame->IsSwapable())
	{
		HRESULT hResult;
			//C: If the active peice is not yet descending, then set a bit that
			//   requests for the pieces to be swapped as soon as the game
			//   starts descending again.
		if (!m_pGame->IsDescending())
		{
			m_isSwapWaiting = true;
		}
		else
		{
			m_isSwapWaiting = false;
			//C: Show the animation.
			hResult = CreateStoreSwap();
			if (FAILED(hResult))
			{
				return hResult;
			}
			//C: Insure that the current piece will not be set by restting the
			//   set timer to 0.
			m_dwStartTime = 0;
		}

		return S_OK;
	}

	return S_FALSE;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		5/15/2002
Purpose:	Event handler for when the game is over.  This function will set the
			state in the game object and display the game end animation.
Parameters:	NONE
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::OnGameOver	()
{
			//C: End the game.
	m_pGame->End();
			//C: Perform the Game end animation.
	HRESULT hr = GameOverAnimation();
			//C: Play the Sound.
	if (m_pSoundGameOver)
	{
		m_pSoundGameOver->Play();
	}

	return hr;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		5/26/2002
Purpose:	Event handler to start the game.  THis function will initialize the
			game state and display for a new game.
Parameters:	NONE
Return:		HRESULT indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::OnStartGame ()
{
	SetState(NORMAL);
			//C: Start the game.
	m_pGame->New();
	m_pGame->Start();
			//C: Update the game board display.
	UpdateBoard(true);
			//C: Reset the rate of descent to 2 lines per second.
	m_dDescentRate = 2.0 / 1000.0;
	m_pGame->SetDescentRate(m_dDescentRate);

	return S_OK;
}

/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Draws the background image for the level.  This function will 
			also draw the start, and scores screen.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawBackground (DDSurface *pSurface)
{
	HRESULT hResult = S_OK;
			//C: Create a rect that is the size of the update buffer.
	if (m_isBackgroundNeeded)
	{
		RECT rClient;
		rClient.left   = 0;
		rClient.top    = 0;
		rClient.right  = m_pUpdateSurface->GetWidth();
		rClient.bottom = m_pUpdateSurface->GetHeight();

		hResult = m_pUpdateSurface->BltFast(0, 0, m_pBackground, &rClient, DDBLTFAST_WAIT);

		m_isBackgroundNeeded = false;
	}

	return hResult;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Draws the tetris board with all of the pieces that have been set
			into place.  This function will update the board image that is
			cached for this view of the board has changed since the last 
			call to this function.
Parameters:	isForcePaint[in]: Indicates if the rows of the board should be
				repainted even if they have not changed.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::UpdateBoard	(bool isForcePaint)
{
	HRESULT hr = S_OK;
			//C: Add two artificial lines that act as boundaries.
	mapLines	lines;
	iterLines	iter;
	TetrisLine	lineBoundary;
	TetrisLine	line;

	memset(lineBoundary.dwID, 0, sizeof(lineBoundary.dwID));
	memset(lineBoundary.nType, 0, sizeof(lineBoundary.nType));

	lines[0xFFFFFFFF]   = lineBoundary;
	lines[BOARD_HEIGHT] = lineBoundary;
			//C:Cycle through all of the rows.  Redraw any rows that have changed.
	UINT index;

	bool isChanged = false;
	for (index = 0; index < BOARD_HEIGHT; ++index)
	{
		if (!isForcePaint && !m_pGame->IsLineChanged(index))
		{
			//C: The line has not changed, continue to the next line.
			continue;
		}
			//C: Get the data for the target line, and the line above and below
			//   the target line.
		iter = lines.find(index - 1);
		if (iter == lines.end())
		{
			m_pGame->GetLine(index-1, line);
			lines[index-1] = line;
		}

		iter = lines.find(index);
		if (iter == lines.end())
		{
			m_pGame->GetLine(index, line);
			lines[index] = line;
		}

		iter = lines.find(index + 1);
		if (iter == lines.end())
		{
			m_pGame->GetLine(index+1, line);
			lines[index+1] = line;
		}
			//C: Draw the target line.
		POINT ptOffset;
//		ptOffset.x = ptBase.x;
//		ptOffset.y = ptBase.y + (index * UNIT_SIZE);
		ptOffset.x = 0;
		ptOffset.y = index * UNIT_SIZE;

		hr = DrawLine(m_pBoard, ptOffset, lines.find(index-1)->second, lines.find(index)->second, lines.find(index+1)->second);
		isChanged = true;
	}
			//C: If the board has been updated, then flush the changes to prevent
			//   the board from being redrawn until another change becomes present.
	if (isChanged)
	{
			//C: Flush the board changes to prevent these lines from being redrawn again.
		m_pGame->FlushChanges();
			//C: Flag board as needing to be updated.
		m_isBoardNeeded = true;
	}

	return hr;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Draws the tetris board with all of the pieces that have been set
			into place.  This function will update the board image that is
			cached for this view of the board has changed since the last 
			call to this function.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawBoard	(DDSurface *pSurface)
{
			//C: Verify input parameters.
	if (!pSurface)
	{
		return E_INVALIDARG;
	}

	HRESULT hResult = S_OK;
			//C: If the board needs to be updated, then paint it.
	if (m_isBoardNeeded)
	{
		pSurface->BltFast(m_rBoard.left, m_rBoard.top, m_pBackground, &m_rBoard, DDBLTFAST_WAIT);

		RECT rBounds;
		rBounds.left = 0;
		rBounds.top = 0;
		rBounds.right = m_pBoard->GetWidth();
		rBounds.bottom = m_pBoard->GetHeight();

		hResult = pSurface->BltFast(m_rBoard.left, m_rBoard.top, m_pBoard, &rBounds, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);

		m_isBoardNeeded = false;
	}

	return hResult;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/14/2002
Purpose:	Draws a single line of the TetrisGame Board.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
			lines[in]: A Block of three lines that will be used to extrapolate
				data from the board.  Only the line found in index 1 will
				be painted, the other two are for reference.
Retudrn:		HRESULT indicating the status of this function.
******************************************************************************/
HRESULT CTetrisView::DrawLine	(DDSurface *pSurface, POINT &ptOffset, TetrisLine &top, TetrisLine &line, TetrisLine &bottom)
{
			//C: Verfiy input parameters.
	if (!pSurface)
	{
		return E_INVALIDARG;
	}
			//C: Test that the requested line is within the limits.
	if (line.index >= BOARD_HEIGHT)
	{
		ATLASSERT(0);
		return E_INVALIDARG;
	}
			//C: Test if this surface contains a colorKey;
	DWORD dwColorKey;
	bool  isColorKey = SUCCEEDED(pSurface->GetSourceColorKey(dwColorKey));
			//C: Render each column of the target line one block at a time.
			//   The way each block is rendered depends on the ID of the piece on
			//   all sides of the target block.
	UINT index;
	RECT rBlock;
	for (index = 0; index < BOARD_WIDTH; ++index)
	{
		DWORD LeftEdgeColor;
		DWORD RightEdgeColor;
		DWORD TopEdgeColor;
		DWORD BottomEdgeColor;
			//C: Get the dimensions for this block.
		rBlock.left = ptOffset.x + (index * UNIT_SIZE);
		rBlock.top  = ptOffset.y;
		rBlock.right  = rBlock.left + UNIT_SIZE;
		rBlock.bottom = rBlock.top + UNIT_SIZE;
			//C: Paint this block according to which type of block it is.
		switch (line.nType[index])
		{
		case PT_EMPTY:
			{
			//C: Fill the empty block.
				pSurface->FillColor(&rBlock, dwColorKey);
			}
			break;
		case PT_LINE:
			{
				pSurface->FillColor(&rBlock, 0x00A379C0);
				LeftEdgeColor	= 0x00B669D7;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00D6AEE7;
				BottomEdgeColor	= 0x004A106D;
			}
			break;
		case PT_LEFT:
			{
				pSurface->FillColor(&rBlock, 0x00DBA7FF);
				LeftEdgeColor	= 0x00A16AE8;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00FAE4FF;
				BottomEdgeColor	= 0x00442375;
			}
			break;
		case PT_RIGHT:
			{
				pSurface->FillColor(&rBlock, 0x00FFFF7A);
				LeftEdgeColor	= 0x00CC7E20;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00FFFFEC;
				BottomEdgeColor	= 0x0083573C;
			}
			break;
		case PT_T:
			{
				pSurface->FillColor(&rBlock, 0x00639CBB);
				LeftEdgeColor	= 0x005B82AA;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00E4F6FF;
				BottomEdgeColor	= 0x00696277;
			}
			break;
		case PT_BLOCK:
			{
				pSurface->FillColor(&rBlock, 0x00B3BCB8);
				LeftEdgeColor	= 0x00A49BA8;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00D1CDD3;
				BottomEdgeColor	= 0x0040749B;
			}
			break;
		case PT_Z:
			{
				pSurface->FillColor(&rBlock, 0x00F397ED);
				LeftEdgeColor	= 0x00954F8A;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00FCD0FE;
				BottomEdgeColor	= 0x00613957;
			}
			break;
		case PT_S:
			{
				pSurface->FillColor(&rBlock, 0x00DAD2B4);
				LeftEdgeColor	= 0x00C09B70;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00FAF3E9;
				BottomEdgeColor	= 0x006A461A;
			}
			break;
		case PT_SILVER:
			{
				pSurface->FillColor(&rBlock, 0x00A0AEA0);
				LeftEdgeColor	= 0x009752C3;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00F0F2F3;
				BottomEdgeColor	= 0x004A5F6E;
			}
			break;
		case PT_GOLD:
			{
				pSurface->FillColor(&rBlock, 0x00EBE394);
				LeftEdgeColor	= 0x00C8C059;
				RightEdgeColor	= LeftEdgeColor;
				TopEdgeColor	= 0x00FBF6B7;
				BottomEdgeColor	= 0x00403D13;
			}
			break;
		}

			//C: Paint the edges for this block if any are required.
		if (PT_EMPTY != line.nType[index])
		{
			DDBuffer frame;
			frame.Initialize(*pSurface);
			//C: Test for the left edge.
			if (0 == index || line.dwID[index] != line.dwID[index-1])
			{
				frame.SetPenColor(LeftEdgeColor);
				frame.MoveTo(CPoint(rBlock.left, rBlock.top));
				frame.LineTo(CPoint(rBlock.left, rBlock.bottom));
				frame.MoveTo(CPoint(rBlock.left + 1, rBlock.top));
				frame.LineTo(CPoint(rBlock.left + 1, rBlock.bottom));
			}
			//C: Test for the right edge.
			if (BOARD_WIDTH == index-1 || line.dwID[index] != line.dwID[index+1])
			{
				frame.SetPenColor(RightEdgeColor);
				frame.MoveTo(CPoint(rBlock.right - 1, rBlock.top));
				frame.LineTo(CPoint(rBlock.right - 1, rBlock.bottom));
				frame.MoveTo(CPoint(rBlock.right - 2, rBlock.top));
				frame.LineTo(CPoint(rBlock.right - 2, rBlock.bottom));
			}

			//C: Test for the top edge.
			if (0 == line.index || line.dwID[index] != top.dwID[index])
			{
				frame.SetPenColor(TopEdgeColor);
				frame.MoveTo(CPoint(rBlock.left, rBlock.top));
				frame.LineTo(CPoint(rBlock.right, rBlock.top));
				frame.MoveTo(CPoint(rBlock.left, rBlock.top + 1));
				frame.LineTo(CPoint(rBlock.right, rBlock.top + 1));
			}
			//C: Test for the bottom edge.
			if (BOARD_HEIGHT == line.index-1 || line.dwID[index] != bottom.dwID[index])
			{
				frame.SetPenColor(BottomEdgeColor);
				frame.MoveTo(CPoint(rBlock.left, rBlock.bottom - 1));
				frame.LineTo(CPoint(rBlock.right, rBlock.bottom - 1));
				frame.MoveTo(CPoint(rBlock.left, rBlock.bottom - 2));
				frame.LineTo(CPoint(rBlock.right, rBlock.bottom - 2));
			}


			pSurface->Unlock();
		}
	}

	return S_OK;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Determines if any of the three preview pieces have changed, and 
			repaints them if necessary.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawPreview(DDSurface *pSurface)
{
	HRESULT hResult;
			//C: Draw each of the three preview pieces.
			//C: Check if the piece has changed.
	PIECE_TYPE pieces[3];
	m_pGame->GetPreviewPieces(pieces);
	int index;
	for (index = 0; index< 3; ++index)
	{
		if (pieces[index] == m_Preview[index])
		{
			//C: The pieces are the same, no  reason to update.
			continue;
		}
			//C: Initialize the background of the preview pane.
		pSurface->BltFast(m_rPreview[index].left, m_rPreview[index].top, m_pBackground, &m_rPreview[index], DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
			//C: Cache the current store piece that has been drawn.
		m_Preview[index] = pieces[index];
			//C: Draw the piece in the preview pane.
		hResult = DrawPiece	(
								pSurface, 
								m_pPieceImages[pieces[index]],
								pieces[index], 
								DIR_0, 
								m_rPreview[index], 
								DP_HCENTER | DP_VCENTER
							);
		if (FAILED(hResult))
		{
			return hResult;
		}
	}

	return S_OK;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Determines if the score needs to be updated, and repaints it
			if necessary.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawScore(DDSurface *pSurface)
{
			//C: Verify the input surface pointer.
	if (!pSurface)
	{
		return E_INVALIDARG;
	}
			//C: Only draw the score if the score changes.
	DWORD dwScore = m_pGame->GetScore();
	if (m_dwScore != dwScore)
	{
			//C: Clear the background area behid the score.
		pSurface->BltFast(m_rScore.left, m_rScore.top, m_pBackground, &m_rScore, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

			//C: Update the score so it is not painted a second time.
		m_dwScore = dwScore;
		TCHAR szScore[16];
		wsprintf(szScore, _T("%d"), m_dwScore);

		m_pBreman32->TextOut(pSurface, m_rScore.left, m_rScore.top, szScore, -1);
	}

	return S_OK;	
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Determines if the store piece has changed and redraws it if 
			necessary.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawStore(DDSurface *pSurface)
{
			//C: Check if the piece in the store has changed.
	PIECE_TYPE store = m_pGame->GetStorePiece();
	if (store == m_Store)
	{
			//C: The pieces are the same, no  reason to update.
		return S_OK;
	}
			//C: Initialize the background of the preview pane.
	pSurface->BltFast(m_rStore.left, m_rStore.top, m_pBackground, &m_rStore, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
			//C: Cache the current store piece that has been drawn.
	m_Store = store;
			//C: Draw the piece in the preview pane.
	return DrawPiece(
					pSurface, 
					m_pPieceImages[store],
					store, 
					DIR_0, 
					m_rStore, 
					DP_HCENTER | DP_VCENTER
					);
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		10/18/2002
Purpose:	Draws the main options screen.
Parameters:	pSurface[in]: A pointer to the surface object to paint the board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawMain (DDSurface *pSurface)
{
	HRESULT hResult = S_OK;

	RECT rClient;
	GetClientRect(&rClient);
	pSurface->FillColor(&rClient, 0x00000000);
			//C: Calculate the position of the Title Screen Text.
	int Width = m_pTitle->GetWidth();
	int Height= m_pTitle->GetHeight();
	RECT rTitle = {0,0,Width, Height};

	POINT pos;
	pos.x = (SCREEN_WIDTH - Width) / 2;
	pos.y = SCREEN_HEIGHT / 2 - Height;
			//C: Draw the Title text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pTitle, &rTitle, DDBLTFAST_WAIT);
			//C: Calculate the area under the title that is available for the text.
	DWORD availableHeight = SCREEN_HEIGHT - (pos.y + Height);
			//C: Calculate the space between each text option.
	DWORD spacer = (availableHeight - (m_pStartText->GetHeight() * 3)) / 6;
			//C: Calculate the pos of the start text.
	pos.x = (SCREEN_WIDTH - m_pStartText->GetWidth()) / 2;
	pos.y = (pos.y + Height) + spacer;
			//C: Draw the start text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pStartText, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
			//C: Calculate the pos of the options text.
	pos.x = (SCREEN_WIDTH - m_pStartText->GetWidth()) / 2;
	pos.y = pos.y + m_pStartText->GetHeight() + spacer;
			//C: Draw the options text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pOptionsText, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
			//C: Calculate the pos of the exit text.
	pos.x = (SCREEN_WIDTH - m_pExitText->GetWidth()) / 2;
	pos.y = pos.y + m_pOptionsText->GetHeight() + spacer;
			//C: Draw the exit text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pExitText, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);

	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		10/18/2002
Purpose:	Draws the gameover options screen.
Parameters:	pSurface[in]: A pointer to the surface object to paint the board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawGameOver (DDSurface *pSurface)
{
	HRESULT hResult = S_OK;
			//C: Verify and update the background image.
	DrawBackground(pSurface);
			//C: Draw the score.
	DrawScore(pSurface);
			//C: Calculate the postion of the Pause Screen Text.
	int Width = m_pGameOver->GetWidth();
	int Height= m_pGameOver->GetHeight();
	RECT rGameOver = {0,0,Width, Height};


	POINT pos;
	pos.x = (SCREEN_WIDTH - Width) / 2;
	pos.y = SCREEN_HEIGHT / 3 - Height;
			//C: Draw the GameOver text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pGameOver, &rGameOver, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
			//C: Calculate the area under the title that is available for the text.
	DWORD availableHeight = SCREEN_HEIGHT - (pos.y + Height);
			//C: Calculate the space between each text option.
	DWORD spacer = (availableHeight - (m_pStartText->GetHeight() * 3)) / 6;
			//C: Calculate the pos of the start text.
	pos.x = (SCREEN_WIDTH - m_pStartText->GetWidth()) / 2;
	pos.y = (pos.y + Height) + spacer;
			//C: Draw the start text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pStartText, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
			//C: Calculate the pos of the options text.
	pos.x = (SCREEN_WIDTH - m_pStartText->GetWidth()) / 2;
	pos.y = pos.y + m_pStartText->GetHeight() + spacer;
			//C: Draw the options text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pOptionsText, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
			//C: Calculate the pos of the ext text.
	pos.x = (SCREEN_WIDTH - m_pExitText->GetWidth()) / 2;
	pos.y = pos.y + m_pOptionsText->GetHeight() + spacer;
			//C: Draw the exit text to the screen.
	pSurface->BltFast(pos.x, pos.y, m_pExitText, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);

	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		10/18/2002
Purpose:	Draws the pause screen.
Parameters:	pSurface[in]: A pointer to the surface object to paint the board onto.
Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT CTetrisView::DrawPause (DDSurface *pSurface)
{
	HRESULT hResult = S_OK;
			//C: Verify and update the background image.
	hResult = DrawBackground(pSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Draw the score.
	hResult = DrawScore(pSurface);
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Calculate the postion of the Pause Screen Text.
	int Width = m_pPause->GetWidth();
	int Height= m_pPause->GetHeight();
	RECT rPause = {0,0,Width, Height};


	POINT pos;
	pos.x = (SCREEN_WIDTH - Width) / 2;
	pos.y = (SCREEN_HEIGHT - Height) / 2;
			//C: Draw the Pause text to the screen.
	return pSurface->BltFast(pos.x, pos.y, m_pPause, &rPause, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/12/2002
Purpose:	Function that animates the change of the preview pieces and the 
			new active piece.
Parameters:	dwMilliSec[in]: The number of milliseconds that the animation 
				should use to complete the sequence.		
			fDelay[in]: The number of seconds to delay the animation before
				it starts.
Return:		HRESULT indicating the status of the function call.
******************************************************************************/
HRESULT CTetrisView::NewPieceAnimation (DWORD dwMilliSec, FLOAT fDelay)
{
	MovePieceAnimation *animation;
			//C: Get the pieces that will be dealt with.
	PIECE_TYPE		active;
	PIECE_TYPE		preview[3];

	active = m_pGame->GetActivePiece();
	m_pGame->GetPreviewPieces(preview);
			//C: Stop the active piece from descending.
	TriggerEvents(STOP_DESCENT);
			//C: Prepare the parameters for each of the three pieces that will
			//   be animated.
	RECT rStart;
	RECT rEnd;
			//C: Preview pane 1.
	animation = new MovePieceAnimation;
	if (!animation)
	{
		return E_OUTOFMEMORY;
	}

	rStart= m_rPreview[0];
	rStart.right  -= rStart.left;
	rStart.bottom -= rStart.top;

	rEnd	= m_rStart;
	rEnd.right  -= rEnd.left;
	rEnd.bottom -= rEnd.top;

	animation->SetStartTime(DDTimer(TIMER_GETABSOLUTETIME) + fDelay);
	animation->Initialize(active, DIR_0, m_pPieceImages[active], rStart, rEnd);
			//C: Erase the current piece that is displayed on the UpdateSurface, and prevent
			//   the piece from being repainted.
	m_pUpdateSurface->BltFast(m_rPreview[0].left, m_rPreview[0].top, m_pBackground, &m_rPreview[0], DDBLTFAST_WAIT);
	m_Preview[0] = preview[0];
			//C: Add a trigger to the animation to force a repaint of this piece when
			//   the animation completes.
	animation->SetTriggerID(REFRESH_PREVIEW1);
			//C: Add this animation to the list of current animations.
	m_lstAnimations.push_back(animation);

			//C: Preview pane 2.
	animation = new MovePieceAnimation;
	if (!animation)
	{
		return E_OUTOFMEMORY;
	}

	rStart= m_rPreview[1];
	rStart.right  -= rStart.left;
	rStart.bottom -= rStart.top;

	rEnd	= m_rPreview[0];
	rEnd.right  -= rEnd.left;
	rEnd.bottom -= rEnd.top;

	animation->SetStartTime(DDTimer(TIMER_GETABSOLUTETIME) + fDelay);
	animation->Initialize(m_Preview[0], DIR_0, m_pPieceImages[preview[0]], rStart, rEnd);
			//C: Erase the current piece that is displayed on the UpdateSurface, and prevent
			//   the piece from being repainted.
	m_pUpdateSurface->BltFast(m_rPreview[1].left, m_rPreview[1].top, m_pBackground, &m_rPreview[1], DDBLTFAST_WAIT);
	m_Preview[1] = preview[1];
			//C: Add a trigger to the animation to force a repaint of this piece when
			//   the animation completes.
	animation->SetTriggerID(REFRESH_PREVIEW2);
			//C: Add this animation to the list of current animations.
	m_lstAnimations.push_back(animation);

			//C: Preview pane 3.
	animation = new MovePieceAnimation;
	if (!animation)
	{
		return E_OUTOFMEMORY;
	}

	rStart= m_rPreview[2];
	rStart.right  -= rStart.left;
	rStart.bottom -= rStart.top;

	rEnd	= m_rPreview[1];
	rEnd.right  -= rEnd.left;
	rEnd.bottom -= rEnd.top;

	animation->SetStartTime(DDTimer(TIMER_GETABSOLUTETIME) + fDelay);
	animation->Initialize(m_Preview[1], DIR_0, m_pPieceImages[preview[1]], rStart, rEnd);
			//C: Erase the current piece that is displayed on the UpdateSurface, and prevent
			//   the piece from being repainted.
	m_pUpdateSurface->BltFast(m_rPreview[2].left, m_rPreview[2].top, m_pBackground, &m_rPreview[2], DDBLTFAST_WAIT);
	m_Preview[2] = preview[2];
			//C: Add a trigger to the animation to force a repaint of this piece when
			//   the animation completes.
	animation->SetTriggerID(REFRESH_PREVIEW3 | START_DESCENT);
			//C: Add this animation to the list of current animations.
	m_lstAnimations.push_back(animation);

	return S_OK;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		10/15/2002
Purpose:	Animates the fading of the currently active piece into the gameboard.
Parameters:	piece[in]: The tetris piece definition of the piece to paint.
			rPos[in]: The position of the piece to paint.
			isSuper[in]: If the piece completes a super block then this animation
				will paint a super block rather than a regular piece.  rPos will
				then represent the boundaries of the super block rather than the
				piece to be set.
Return:		HRESULT indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::CreateSetPiece(TETRIS_PIECE& piece, RECT& rPos, bool isSuper)
{
	HRESULT		hResult	= S_OK;
	FLOAT		fCurTime = DDTimer(TIMER_GETABSOLUTETIME);
			//C: Verify that the surfaces are valid.
	if (!m_pBackSurface)
	{
		return E_POINTER;
	}
			//C: Calculate the dimensions of the area to clear.
			//   THis will be the dimensions of the board, plus the block size
			//   added to the top of the rectangle.
#pragma message( REMIND( "PMW: Revisit this." ) )

	return S_OK;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		5/18/2002
Purpose:	Animates all of the pieces falling to fill in spaces for lines that
			have been cleared.
Parameters:	sLines[in]: The set of indices for the lines that have been cleared.
Return:		HRESULT indicates the status of this function.
******************************************************************************/
HRESULT CTetrisView::CreateCollapseBoard(setUINT sLines, FLOAT fDelay)
{
			//C: If there are no entries in sLines, then exit.
	if (0 == sLines.size()) 
	{
		return S_OK;
	}

	HRESULT hResult = S_OK;
			//C: Determine if there is a gap in the set of lines.
	UINTRIter iter = sLines.rbegin();
	UINT	 nLast = BOARD_HEIGHT;
	UINT	 nCount= 0;
	for (; iter != sLines.rend(); ++iter, ++nCount)
	{
		if (nLast != (*iter)+1 && iter != sLines.rbegin())
		{
			//C: There is a break in the sequence of lines. Start a new set
			//   of falling blocks.
			hResult = CreateLineDrop((*iter)+1, nLast, nCount, fDelay, false);
			if (FAILED(hResult))
			{
				return hResult;
			}
		}

		nLast = *iter;
	}

	hResult = CreateLineDrop(0, nLast, nCount, fDelay, true);
			//C: Flush the changes in the gameboard to prevent the board from
			//   being drawn while it is being animated.
	m_pGame->FlushChanges();

	return hResult;
}


/* Public *********************************************************************
Author:		Paul Watt
Date:		5/18/2002
Purpose:	Makes a section of the board fall until all of the empty lines 
			are filled.
Parameters:	nStart[in]: Index that indicates the first line in the block that
				needs to drop.
			nEnd[in]: Indicates the index of the last line that needs to drop
				in the set.  nEnd can be equal to nStart, but it cannot be
				less than nSTart.
			nDropCount[in]: The number of lines that the board will be dropping.
			fDelay[in]: How long to delay the animation from the current time.
			isPaintBoard[in]: Should the current line force a board repaint
				after it completes its animation.
Return:		HRESULT indicates the status if this function.
******************************************************************************/
HRESULT CTetrisView::CreateLineDrop(UINT nStart, UINT nEnd, UINT nDropCount, FLOAT fDelay, bool isPaintBoard)
{
			//C: Verify input parameters.
	if (nEnd < nStart)
	{
		return E_INVALIDARG;
	}

	HRESULT hResult = S_OK;
			//C: Create a surface that will hold the lines that should be dropped.
	UINT	nRowCount = nEnd - nStart;
	DWORD	dwWidth	= BOARD_WIDTH * UNIT_SIZE;
	DWORD	dwHeight= nRowCount * UNIT_SIZE;

	DDOffScreenSurface *pSegment = ::CreateOffScreenSurface(m_pDD, dwWidth, dwHeight);
	if (!pSegment)
	{
		return E_FAIL;
	}
			//C: Set a source color key, for the segment surface.
	pSegment->SetSourceColorKey(0x00000000);
			//C: Draw the current board for these lines onto the segment surface.
	POINT ptOffset = {0,0};
	RECT rBounds;
	rBounds.left	= 0;
	rBounds.top		= nStart * UNIT_SIZE;
	rBounds.right	= rBounds.left + dwWidth;
	rBounds.bottom	= rBounds.top + dwHeight;
	pSegment->BltFast(0, 0, m_pBoard, &rBounds, DDBLTFAST_WAIT); 
	if (FAILED(hResult))
	{
		return hResult;
	}
			//C: Calculate the current rectangle that these blocks occupy.
	RECT rTarget;
	rTarget.left	= m_rBoard.left;
	rTarget.top		= m_rBoard.top + (nStart * UNIT_SIZE);
	rTarget.right	= rTarget.left + dwWidth;
	rTarget.bottom	= rTarget.top + dwHeight;
			//C: Calculate the height that the blocks need to fall.
	UINT nTotalDrop = nDropCount * UNIT_SIZE;
			//C: Create the line drop animation.
	DropLinesAnimation *animation = new DropLinesAnimation();
	if (!animation)
	{
		delete pSegment;
		return E_OUTOFMEMORY;
	}
			//C: Initialize the animation.
	animation->SetStartTime(DDTimer(TIMER_GETABSOLUTETIME) + fDelay);
	animation->Initialize(rTarget, pSegment, nTotalDrop); 
			//C: Erase the lines that have been cleared.
	RECT rClear;
	rClear.left		= m_rBoard.left;
	rClear.top		= m_rBoard.top + nStart * UNIT_SIZE;
	rClear.right	= rClear.left + dwWidth;
	rClear.bottom	= rClear.top + dwHeight + nTotalDrop;
	m_pUpdateSurface->BltFast(rClear.left, rClear.top, m_pBackground, &rClear, DDBLTFAST_WAIT);

	RECT rBoardClear;
	rBoardClear.left	= 0;
	rBoardClear.top		= nStart * UNIT_SIZE;
	rBoardClear.right	= dwWidth;
	rBoardClear.bottom	= rBoardClear.top + dwHeight + nTotalDrop;
	m_pBoard->FillColor(&rBoardClear, 0x00000000);
			//C: IF the board should be painted, then repaint the board.
	if (isPaintBoard)
	{
		animation->SetTriggerID(REFRESH_BOARD);
			//C: Play a sound for this version of the line as well.
		animation->SetEndSound(m_pSoundBang);
	}
			//C: Add this animation to the list of current animations.
	m_lstAnimations.push_back(animation);

	return hResult;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		5/15/2002
Purpose:	Animates a line exploding from the board.
Parameters:	sLines[in]: A Set of line indices that indicate the lines to be
				removed from the board..
Return:		HRESULT indicates the status of this function.  If the animation 
			has completed, then S_OK will be returned, if the function has 
			succeeded, but the animation is still progressing, then S_FALSE
			will be returned.
******************************************************************************/
HRESULT CTetrisView::CreateLineExplosion(setUINT sLines, DDSurface *pBoard, FLOAT fDelay)
{
			//C: If there are no entries in sLines, then exit.
	if (0 == sLines.size()) 
	{
		return S_OK;
	}

	HRESULT		hResult	= S_OK;
			//C: Verify that the surfaces are valid.
	if (!m_pBackSurface || !pBoard)
	{
		return E_POINTER;
	}
			//C: Precalculate values.
	int BoardWidth  = BOARD_WIDTH * UNIT_SIZE;
	int BoardHeight = BOARD_HEIGHT * UNIT_SIZE;

	try
	{
			//C: Make the GameBoard collapse.
		hResult = CreateCollapseBoard(sLines, 0.15f + fDelay);
		if (FAILED(hResult))
		{
			throw hResult;
		}
			//C: Calculate the dimensions of the area to clear.
			//   THis will be the dimensions of the board, plus the block size
			//   added to the top of the rectangle.
			//C: Get the current time.
		FLOAT fCurTime;
		UINT index = 0;
		LineExplosionAnimation *animation;
		for (UINTRIter iter = sLines.rbegin();iter != sLines.rend(); ++iter, ++index)
		{
			//C: Calculate that portion of the board that will be used.
			RECT rBoard;
			rBoard.left		= 0;
			rBoard.top		= (UNIT_SIZE * (*iter));
			rBoard.right	= m_pBoard->GetWidth();
			rBoard.bottom	= rBoard.top + UNIT_SIZE;
			//C: Create the surface to store the line.
			DDOffScreenSurface *pSurface;
			pSurface = ::CreateOffScreenSurface(m_pDD, BoardWidth, UNIT_SIZE);
			if (NULL == pSurface)
			{
				throw (HRESULT)E_POINTER;
			}
			//C: Cache the current image from the UpdateSurface.
			pSurface->BltFast(0,0, pBoard, &rBoard, 0);
			//C: Create the line explosion animation.
			animation = new LineExplosionAnimation();
			if (!animation)
			{
				delete pSurface;
				throw (HRESULT)E_OUTOFMEMORY;
			}
			//C: Initialize the animation.
			fCurTime = DDTimer(TIMER_GETABSOLUTETIME);
			FLOAT fStartTime = fCurTime + (0.1f * index) + fDelay;
			animation->SetStartTime(fStartTime);
			//C: Calculate the placement of the exploding segments.
			RECT rBound;
			rBound.left		= m_rBoard.left;
			rBound.top		= m_rBoard.top + (UNIT_SIZE * (*iter));
			rBound.right	= m_rBoard.right;
			rBound.bottom	= rBound.top + UNIT_SIZE;

			animation->Initialize(rBound, pSurface); 
			animation->SetStartSound(m_pSoundTumble);
			//C: Add this animation to the list of current animations.
			m_lstAnimations.push_back(animation);
		}
	}
	catch (HRESULT herr)
	{
		hResult = herr;
	}

	return hResult;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		4/13/2002
Purpose:	Animation for when the current piece is swapped with the store piece.
Parameters:	dwMilliSec[in]: The number of milliseconds that the animation 
				should use to complete the sequence.		
Return:		HRESULT indicating the status of the function call.
******************************************************************************/
HRESULT CTetrisView::CreateStoreSwap()
{
	MovePieceAnimation *animation;
			//C: Cache the current active and store pieces as well as the current
			//   piece position.
	PIECE_TYPE	active = m_pGame->GetActivePiece();
	PIECE_TYPE	store  = m_pGame->GetStorePiece();
	int			X	= m_pGame->GetPiecePosX();
	double		Y	= m_pGame->GetPiecePosY();
			//C: Prevent the active piece from descending.
	TriggerEvents(STOP_DESCENT);
			//C: Swap the pieces.
	m_pGame->Swap();
			//C: Prepare the parameters for each of the three pieces that will
			//   be animated.
	RECT rStart;
	RECT rEnd;
			//C: Active Piece.
	animation = new MovePieceAnimation;
	if (!animation)
	{
		return E_OUTOFMEMORY;
	}

	rStart.left	= X * UNIT_SIZE + m_rBoard.left;
	rStart.top	= Y * UNIT_SIZE + m_rBoard.top;
	rStart.right	= BLOCK_SIZE;
	rStart.bottom	= BLOCK_SIZE;

	rEnd	= m_rStore;
	rEnd.right  -= rEnd.left;
	rEnd.bottom -= rEnd.top;

	animation->Initialize(active, DIR_0, m_pPieceImages[active], rStart, rEnd);
			//C: Add this animation to the list of current animations.
	m_lstAnimations.push_back(animation);
	
			//C: The store piece
	animation = new MovePieceAnimation;
	if (!animation)
	{
		return E_OUTOFMEMORY;
	}

	rStart	= m_rStore;
	rStart.right	-= BLOCK_SIZE;
	rStart.bottom	= BLOCK_SIZE;

	rEnd	= m_rStart;
	rEnd.right  -= rEnd.left;
	rEnd.bottom -= rEnd.top;

	animation->Initialize(store, DIR_0, m_pPieceImages[store], rStart, rEnd);
			//C: Erase the current piece that is displayed on the UpdateSurface, and prevent
			//   the piece from being repainted.
	m_pUpdateSurface->BltFast(m_rStore.left, m_rStore.top, m_pBackground, &m_rStore, DDBLTFAST_WAIT);
	m_Store = m_pGame->GetStorePiece();
			//C: Add a trigger to the animation to force a repaint of this piece when
			//   the animation completes.
	animation->SetTriggerID(REFRESH_STORE | START_DESCENT);
			//C: Add this animation to the list of current animations.
	m_lstAnimations.push_back(animation);

	return S_OK;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		5/15/2002
Purpose:	Animates a line fading into the background.
Parameters:	nLine[in]: Indicates the position of the line being removed.
Return:		HRESULT indicates the status of this function.

Note:		This animation is synchronous, therefore it will stop gameplay while
			this animation is playing.  This function takes approximately 
			1 second to complete.
******************************************************************************/
HRESULT CTetrisView::CreateLineFade(setUINT sLines)
{
			//C: If there are no entries in sLines, then exit.
	if (0 == sLines.size()) 
	{
		return S_FALSE;
	}

	HRESULT		hResult	= S_FALSE;
	FLOAT		fCurTime = DDTimer(TIMER_GETABSOLUTETIME);
			//C: Verify that the surfaces are valid.
	if (!m_pBackSurface)
	{
		return E_POINTER;
	}
			//C: Calculate the dimensions of the area to clear.
			//   THis will be the dimensions of the board, plus the block size
			//   added to the top of the rectangle.
	UINT index = 0;
	UINTIter iter = sLines.begin();
	LineFadeAnimation *animation;
	for (index = 0;iter != sLines.end(); ++iter, ++index)
	{
		RECT rTarget;
		rTarget.left	= m_rBoard.left;
		rTarget.top		= m_rBoard.top + (UNIT_SIZE * (*iter));
		rTarget.right	= m_rBoard.right;
		rTarget.bottom	= rTarget.top + UNIT_SIZE;
			//C: Create the surface to store the background.
		DDOffScreenSurface *pSurface;
		pSurface = ::CreateOffScreenSurface(m_pDD, BOARD_WIDTH * UNIT_SIZE, UNIT_SIZE);
		if (NULL == pSurface)
		{
			return E_POINTER;
		}
			//C: Cache the current image from the Background Surface.
		pSurface->BltFast(0,0, m_pBackground, &rTarget, 0);
		m_pUpdateSurface->BltFast(rTarget.left, rTarget.top, m_pBackground, &rTarget, 0);
			//C: Create the line fade animation object.
		animation = new LineFadeAnimation();
		if (!animation)
		{
			delete pSurface;
			return E_OUTOFMEMORY;
		}
			//C: Initialize the animation.
		animation->SetStartTime(fCurTime);
		animation->Initialize(m_pDD, rTarget, pSurface); 
			//C: Add this animation to the list of current animations.
		m_lstAnimations.push_back(animation);
	}
			//C: Play a sound for the last line that fades.
	animation->SetStartSound(m_pSoundFade);
			//C: Make the GameBoard collapse.
	hResult = CreateCollapseBoard(sLines, 1.0f);
	if (FAILED(hResult))
	{
		return hResult;
	}

	return S_OK;
}


/* Protected ******************************************************************
Author:		Paul Watt
Date:		5/15/2002
Purpose:	Animation that indicates the game is over.  This animation will 
			play synchronously.  Each column of blocks will simply fall straight
			down in a random order at randomly spaced intervals.
Parameters:	NONE
Return:		HRESULT indicates the status of this function.

Note:		This animation is synchronous, therefore it will stop gameplay while
			this animation is playing.  This function takes approximately 
			1 second to complete.
******************************************************************************/
HRESULT CTetrisView::GameOverAnimation ()
{
			//C: Verify that the surfaces are valid.
	if (!m_pBackSurface || !m_pBoard)
	{
		return E_POINTER;
	}
			//C: Precalculate values.
	int BoardWidth  = BOARD_WIDTH * UNIT_SIZE;
	int BoardHeight = BOARD_HEIGHT * UNIT_SIZE;

	RECT rBounds	= {0,0,m_pUpdateSurface->GetWidth(), m_pUpdateSurface->GetHeight()};
	RECT rColumn	= {0, 0, UNIT_SIZE, BoardHeight};

			//C: Create 10 images.  Each image is a column that is the height of
			//   the game board.
	DDOffScreenSurface *pColumns[BOARD_WIDTH];
	DWORD index;

	DWORD columns[BOARD_WIDTH];
	for (index = 0; index < BOARD_WIDTH; index++)
	{
			//C: Create the surface.
		pColumns[index] = ::CreateOffScreenSurface(m_pDD, UNIT_SIZE, BoardHeight);
		pColumns[index]->SetSourceColorKey(0x00000000);
			//C: Initialize the surface.
		RECT rBoard;
		rBoard.left		= index * UNIT_SIZE;
		rBoard.top		= 0;
		rBoard.right	= rBoard.left + UNIT_SIZE;
		rBoard.bottom	= BoardHeight;
		pColumns[index]->BltFast(0, 0, m_pBoard, &rBoard, DDBLTFAST_WAIT);

		columns[index] = index;
	}
			//C: Create an ordering schedule for how all of the columns will fall.
	FLOAT delays[BOARD_WIDTH];
	FLOAT totalTime = 0.0f;
	for (index = BOARD_WIDTH; index > 0; index--)
	{
		DWORD pos = rand() % index;
			//C: Once an index has been chosen, swap the value of the pos
			//   with the current index.
		DWORD temp			= columns[pos];
		columns[pos]		= columns[index-1];
		columns[index-1]	= temp;
			//C: Choose a random delay time for the columns as well. (between 0 and 100 milliseconds.)
		delays[BOARD_WIDTH - index] = totalTime + ((rand() % 100) / 1000.0f);
		totalTime = delays[BOARD_WIDTH - index];
	}
			//C: Initialize the display surfaces for this animation.
	m_isBackgroundNeeded = true;
	DrawBackground(m_pUpdateSurface);
			//C: Draw the score.
	m_dwScore = 0xFFFFFFFF;
	DrawScore(m_pUpdateSurface);
			//C: Start the animation where the blocks fall.	
	totalTime = 0;
	DDTimer(TIMER_GETELAPSEDTIME);

	FLOAT startTimes[BOARD_WIDTH];
	::FillMemory(startTimes, sizeof(FLOAT) * BOARD_WIDTH, 0);
			//C: Start the timer to calculate the animation.
	bool  isDone = false;
	bool  isSoundPlayed[BOARD_WIDTH];
	FillMemory(isSoundPlayed, sizeof(bool) * BOARD_WIDTH, false);

	while (!isDone)
	{
		isDone = true;
			//C: Refresh the back surface with the update surface.
		m_pBackSurface->BltFast(0, 0, m_pUpdateSurface, &rBounds, DDBLTFAST_WAIT);
			//C: Get the current time.
		totalTime += DDTimer(TIMER_GETELAPSEDTIME);
			//C: Cycle through each of the columns and determine their current position.
		for (index = 0; index < BOARD_WIDTH; index++)
		{
			RECT	rCurColumn	= rColumn;
			DWORD	x			= m_rBoard.left + (columns[index] * UNIT_SIZE);
			DWORD	y			= m_rBoard.top;
			//C: If the current time is past the delay time, adjust the height of the column placement.
			if (totalTime > delays[index])
			{
			//C: Calculate the base time for this animation.
				FLOAT elapsed = totalTime - delays[index];
			//C: Calculate the height for the current column of blocks.
			//C: The gravity is cut in half from the regular model to prolong the animation.
				y = m_rBoard.top + (0.5 * GRAVITY * elapsed * elapsed);

				if (!isSoundPlayed[index])
				{
					if (m_pSoundDrop)
					{
						m_pSoundDrop->Play();
					}

					isSoundPlayed[index] = true;
				}
			}
			//C: If the placement of the column is > the screen height, then the 
			//   animation is not yet complete.
			if (y > SCREEN_HEIGHT)
			{
				continue;
			}
			//C: Clip the dimensions of the current column rect if they extend past the bottom
			//   of the screen.
			if (rCurColumn.bottom + y > SCREEN_HEIGHT)
			{
				rCurColumn.bottom = SCREEN_HEIGHT - y - 1;
			}
			//C: Paint the current column.
			m_pBackSurface->BltFast(x, y, pColumns[columns[index]], &rCurColumn, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);
			//C: If execution reaches this point, the animation is not yet complete.
			isDone = false;
		}
			//C: Update the screen.
		Flip();
	}
			//C: Paint the Game Over text in the center of the screen with a
			//   list of the users options.
	SetState(GAMEOVER);
			//C: Free all of the offscreen surfaces.
	for (index = 0; index < BOARD_WIDTH; index++)
	{
		delete pColumns[index];
	}

	return S_OK;
}



/* Global *********************************************************************
Author:		Paul Watt
Date:		4/6/2002
Purpose:	Draws the a piece, at the specified location on pSurface.
Parameters:	pSurface[in]: A pointer to the surface object to paint the 
				board onto.
			piece[in]: The type of piece to draw.
			rLoc[in]: The rectangle in which to paint the piece.
			dwFlags[in]: Flags that determine how the piece is drawn.  Each flag
				is described below.  All of the flags in the groups are mutually
				exclusive of one another.

				DP_LEFT:	Paints the piece on the left border of rLoc
				DP_RIGHT:	Paints the piece on the right border of rLoc
				DP_HCENTER: Centers the piece horizontally in rLoc.

				DP_TOP:		Paints the piece on the top border of rLoc.
				DP_BOTTOM:	Paints the piece on the bottom border of rLoc.
				DP_VCENTER: Centers the piece vertically in rLoc.

				DP_SHADOW:	Paints the piece as a translucent shadow.  This is
					accmplished by alpha blending the piece with the background.

Return:		HRESULT indicates the status of this function call.
******************************************************************************/
HRESULT DrawPiece(DDSurface *pSurface, DDSurface *pBlock, PIECE_TYPE piece, UINT nDir, RECT &rLoc, DWORD dwFlags)
{
	HRESULT hResult = S_OK;
			//C: Verify the input parameters.
	if (!pSurface || !pBlock || (piece <= PT_EMPTY || piece > PT_COUNT) || nDir > DIR_270)
	{
		ATLASSERT(0);
		return E_INVALIDARG;
	}
			//C: Verify that the piece will fit in the display rectangle.
	int rLocWidth  = rLoc.right - rLoc.left;
	int	rLocHeight = rLoc.bottom - rLoc.top;
	if (BLOCK_SIZE > rLocWidth ||
		BLOCK_SIZE > rLocHeight)
	{
			//C: The dimensions that the user gave us are not large enough.
		return E_INVALIDARG;
	}
			//C: Determine the placement of the piece horizontally.
	int x;
	int y;
	if (dwFlags & DP_HCENTER)
	{
		x = rLoc.left + (rLocWidth - BLOCK_SIZE) / 2;
	}
	else if(dwFlags & DP_RIGHT)
	{
		x = rLoc.right - BLOCK_SIZE;
	}
	else
	{
			//C: DP_LEFT is the default if none are specified.
		x = rLoc.left;
	}
			//C: Determine the placement of the piece vertically.
	if (dwFlags & DP_VCENTER)
	{
		y = rLoc.top + (rLocWidth - BLOCK_SIZE) / 2;
	}
	else if(dwFlags & DP_BOTTOM)
	{
		y = rLoc.bottom - BLOCK_SIZE;
	}
	else
	{
			//C: DP_top is the default if none are specified.
		y = rLoc.top;
	}
			//C: Determine if the piece should be painted as a shadow.
	if (dwFlags & DP_SHADOW)
	{
		BLENDFUNCTION blend;
		::ZeroMemory(&blend, sizeof(BLENDFUNCTION));
		blend.SourceConstantAlpha = 127;

		DDBuffer frame;
		if (!frame.Initialize(*pSurface))
		{
			return E_POINTER;
		}

		int iColumn;
		int iRow;
		for (iRow = 0; iRow < 4; ++iRow)
		{
			for (iColumn = 0; iColumn < 4; ++iColumn)
			{
				//C: Test if the current cell has a block in it.
				if (0 == g_Pieces[piece][nDir].cells[iRow][iColumn])
				{
					continue;
				}

				if (!frame.AlphaBlend(x + (iColumn * UNIT_SIZE), y + (iRow * UNIT_SIZE), pBlock, 0, 0, UNIT_SIZE, UNIT_SIZE, blend))
				{
					hResult = E_FAIL;
				}
			}
		}
				//C: Unlock the surface.
		pSurface->Unlock();
	}
	else
	{
		RECT rImage = {0,0,UNIT_SIZE, UNIT_SIZE};

		int iColumn;
		int iRow;
		for (iRow = 0; iRow < 4; ++iRow)
		{
			for (iColumn = 0; iColumn < 4; ++iColumn)
			{
				//C: Test if the current cell has a block in it.
				if (0 == g_Pieces[piece][nDir].cells[iRow][iColumn])
				{
					continue;
				}

			//C: Blt the image to the destination surface.
				pSurface->BltFast(x + (iColumn * UNIT_SIZE), y + (iRow * UNIT_SIZE), pBlock, &rImage, DDBLTFAST_WAIT);
			}
		}
	}

	return hResult;
}


/* Global *********************************************************************
Author:		Paul Watt
Date:		10/19/2002
Purpose:	Calculates a random orientation for an image.  This function
			will rotate an image 0, 90, 180, or 270 degrees and determine
			if the image should be mirrored along either the horizontal or 
			vertical access.
Parameters:	fx[out]:  The ddwFX field will be set of this structure.
Return:		HRESULT will indicate the status if this function.
******************************************************************************/
HRESULT GetRandomOrientation (DDBLTFX &fx)
{
			//C: Randomly rotate this surface by 0, 90, 180, or 360 degrees
			//   and randomly mirror the image verically or horizontally.
	int rotate	= rand() % 4;
	int mirror	= rand() % 3;
	switch (rotate)
	{
		case 0:
			//C: No rotation.
			break;
		case 1:
			fx.dwDDFX |= DDBLTFX_ROTATE90;
			break;
		case 2:
			fx.dwDDFX |= DDBLTFX_ROTATE180;
			break;
		case 3:
			fx.dwDDFX |= DDBLTFX_ROTATE270;
			break;
	}
	switch (mirror)
	{
		case 0:
			//C: No mirror.
			break;
		case 1:
			fx.dwDDFX |= DDBLTFX_MIRRORLEFTRIGHT;
			break;
		case 2:
			fx.dwDDFX |= DDBLTFX_MIRRORUPDOWN;
			break;
	}

	return S_OK;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
United States United States
I am a software architect and I have been developing software for nearly two decades. Over the years I have learned to value maintainable solutions first. This has allowed me to adapt my projects to meet the challenges that inevitably appear during development. I use the most beneficial short-term achievements to drive the software I develop towards a long-term vision.

C++ is my strongest language. However, I have also used x86 ASM, ARM ASM, C, C#, JAVA, Python, and JavaScript to solve programming problems. I have worked in a variety of industries throughout my career, which include:
• Manufacturing
• Consumer Products
• Virtualization
• Computer Infrastructure Management
• DoD Contracting

My experience spans these hardware types and operating systems:
• Desktop
o Windows (Full-stack: GUI, Application, Service, Kernel Driver)
o Linux (Application, Daemon)
• Mobile Devices
o Windows CE / Windows Phone
o Linux
• Embedded Devices
o VxWorks (RTOS)
o Greenhills Linux
o Embedded Windows XP

I am a Mentor and frequent contributor to CodeProject.com with tutorial articles that teach others about the inner workings of the Windows APIs.

I am the creator of an open source project on GitHub called Alchemy[^], which is an open-source compile-time data serialization library.

I maintain my own repository and blog at CodeOfTheDamned.com/[^], because code maintenance does not have to be a living hell.

Comments and Discussions