/* 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;
}