📄 gamemanager.cpp
字号:
/*****************************************************************************
*
* GameManager.cpp
*
* Electrical Engineering Faculty - Software Lab
* Spring semester 1998
*
* Tanks game
*
* Module description: The core of the game - implements the main game loop.
* Holds the list of objects participating in the game.
* In every game iteration, all the objects on the list
* are refreshed, and the game display is updated.
* More details on the algorithms in use are described below.
*
* Authors: Eran Yariv - 28484475
* Moshe Zur - 24070856
*
*
* Date: 23/09/98
*
******************************************************************************/
#include "stdafx.h"
#include <afxmt.h>
#include "tanks.h"
#include "DIB.h"
#include "GameManager.h"
#include "ImageManager.h"
#include "MsgQueue.h"
#include "Bonus.h"
#include "GameBoard.h"
#include "TankObj.h"
#include "Shell.h"
#include "Bullet.h"
#include "Mine.h"
#include "Bomber.h"
#include "Message.h"
#include "GameOver.h"
CGameManager::CGameManager (UINT uFreq) :
m_Timer(TANKS_APP->m_gTimer),
m_ImageManager(TANKS_APP->m_gImageManager),
m_MsgQueue(TANKS_APP->m_gIncomingMsgQueue),
m_CommManager(TANKS_APP->m_gCommManager),
m_dwBonusTimeout (0)
{
VERIFY (SetFrequency (uFreq));
}
BOOL CGameManager::SetFrequency (UINT uFreq)
{
if (uFreq < MIN_RENDER_FREQ || uFreq > MAX_RENDER_FREQ)
return FALSE;
m_uFreq = uFreq;
m_uMilliSleep = UINT (double(1000.0) / double(m_uFreq));
return TRUE;
}
UINT CGameManager::ThreadEntry (LPVOID /*lpParam*/)
{
SetPriority (GAME_MANAGER_THREAD_PRIORITY);
m_MapHWnd = TANKS_APP->m_gHwndMap; // Get handle to map (Windows handle)
ASSERT (NULL != m_MapHWnd);
m_dwChkSumSignal = 0; // Don't send check sum right away
m_iNumTanks = 0; // Initially, no tanks.
m_iLocalTankID = -1; // Initially, no local tank
HDC dc = ::GetDC (m_MapHWnd);
m_BackBuffer.CreateEmpty (MAP_WIDTH, MAP_HEIGHT);// Create back (off-screen) buffer
m_BackBuffer.GetPaletteFromResourceBitmap (IDB_BULLET);
TANKS_APP->m_gDrawDIB.SetPalette (*m_BackBuffer.m_pPalette);
TANKS_APP->m_gDrawDIB.Realize (dc, FALSE);
::ReleaseDC (m_MapHWnd, dc);
MultiRectGameTechnique (); // Do the game main loop
DestroyObjects (); // Kill list of objects
return 0;
}
void CGameManager::DestroyObjects ()
{
m_GameObjsList.KillList();
m_TanksCS.Lock();
for (int i=0; i<MAX_TANKS; i++)
m_pTanks[i] = NULL;
m_TanksCS.Unlock();
m_BonusCS.Lock();
m_pBonus = NULL;
m_BonusCS.Unlock();
m_iNumTanks = 0;
}
#pragma warning (disable : 4701)
/* warning C4701: local variable 'CurObjHImg' may be used without having been initialized
God damn it, trust me on this one - I know what I'm doing here....
*/
/*------------------------------------------------------------------------------
Function: MultiRectGameTechnique
Purpose: Main game loop. Every iteration of the loop, game objects recalc
their status, and the display is refreshed. Every Iteration lasts a
constant period of time, to allow a steady frame rate of the display,
using the sleep system call, to stay low on CPU use.
Input: None.
Output: None.
Remarks: The algorithm in use is explained in details in the Programmers's
manual section in the game's help files.
------------------------------------------------------------------------------*/
void
CGameManager::MultiRectGameTechnique ()
{
#ifdef GATHER_RENDERING_STATS
DWORD dwLoopCount=0;
DWORD dwStartTime = GetTickCount();
int lTotalTimeLeft = 0, // For statistics only !
lMaxTimeLeft = 0, // For statistics only !
lMinTimeLeft = LONG(m_uMilliSleep), // For statistics only !
iMaxObjs = 0; // For statistics only !
#endif // GATHER_RENDERING_STATS
BOOL bImageChanged;
typedef struct {
HIMAGE himg;
CPoint ObjectPos;
} UpdateArrayUnit;
UpdateArrayUnit UpdateArray[MAX_POSSIBLE_OBJECTS];
UINT uArrIndex;
LONG lSleepTime; // Time left to sleep (may be negative if loop is too slow)
m_bRefreshAll = TRUE;
// Initially, empty the message queue to add the board object to the game objects list
EmptyMsgQ(dwStartTime);
// First, create the constant board in the back-buffer
LIST_POS lp = m_GameObjsList.GetHeadPosition();
CGameObject* pBoard = m_GameObjsList.GetNext(lp);
ASSERT (pBoard->GetType() == BOARD);
CDIB *pBoardDIB = m_ImageManager.ExposeDIB (pBoard->GetImage());
// Copy the board directly - no transparency
m_BackBuffer.CopyFrom (pBoardDIB);
while (!m_bEndThread)
{ // Loop while game lasts
// Get sample time
DWORD dwCurTime = m_Timer.GetLocalTime();
DWORD dwLoopStart = dwCurTime;
AttemptToSendGameChecksum ();
if (m_CommManager.IsHost())
TryToAddBonus (dwCurTime);
// Empty object list requests queue
bImageChanged = EmptyMsgQ(dwCurTime);
// Init update rectangle
m_UpdateRegionRect.SetRectEmpty();
// Init updates array
uArrIndex = 0;
//
// First pass: loop the objects
//
// Walk over game objects and update rectangle
// Skip the board:
LIST_POS lp = m_GameObjsList.GetHeadPosition();
CGameObject* pGameObj = m_GameObjsList.GetNext(lp);
ASSERT (pGameObj->GetType() == BOARD);
// Loop the other (transparent non-board) objects:
for ( pGameObj = m_GameObjsList.GetNext(lp);
pGameObj;
pGameObj = m_GameObjsList.GetNext(lp) )
{
StateType CurObjState = pGameObj->CalcState(dwCurTime);
HIMAGE CurObjHImg;
// Get new position from current object
CPoint CurObjPos = pGameObj->GetPos();
// Get update rectangle from current object
CRect CurObjUpdateRect = pGameObj -> GetUpdateRectangle();
// Update region to be union of all rectangles
m_UpdateRegionRect |= CurObjUpdateRect;
if (STATE_DEAD == CurObjState)
{ // Object is no longer with us:
BOOL fIsMine = FALSE;
if (pGameObj->GetType() == MINE)
{
fIsMine = TRUE;
}
else if (pGameObj->GetType() == TANK)
{
m_TanksCS.Lock();
// A tank is dying, remove it from fast pointer array
ASSERT (NULL != m_pTanks[pGameObj->GetID()]); // Tank must still exist
int iID = pGameObj->GetID();
if (m_iLocalTankID == iID)
{ // Local tank is removed
m_CommManager.NotifyExplodingTank(m_iLocalTankID);
m_iLocalTankID = -1;
}
m_pTanks[iID] = NULL;
m_iNumTanks --;
ASSERT (m_iNumTanks >= 0);
m_TanksCS.Unlock();
}
else if (pGameObj->GetType() == BONUS)
{
m_BonusCS.Lock();
// A bonus is dying, remove it from fast pointer
if (m_pBonus == pGameObj)
// If the current bonus is dying (not a newer one)
m_pBonus = NULL;
m_BonusCS.Unlock();
}
if (fIsMine)
m_MinesCS.Lock();
m_GameObjsList.RemoveObject(pGameObj);
if (fIsMine)
m_MinesCS.Unlock();
// If object is removed, map image has changed
bImageChanged = TRUE;
}
else
{ // STATE_ALIVE: Update update rect size:
CurObjHImg = pGameObj->GetImage(); // This call can change the bImageChanged of the object
if (!bImageChanged)
// No change detected yet....
// Ask the current object if it changed image
bImageChanged = pGameObj->HasImageChanged();
}
// Clear back-buffer from previous frame
m_BackBuffer.CopyRectFrom (pBoardDIB,
CurObjUpdateRect.left,
CurObjUpdateRect.top,
CurObjUpdateRect.Width(),
CurObjUpdateRect.Height(),
CurObjUpdateRect.left,
CurObjUpdateRect.top);
// If the object is still alive, add it to the array:
if (CurObjState == STATE_ALIVE)
{
ASSERT (uArrIndex < MAX_POSSIBLE_OBJECTS);
UpdateArray[uArrIndex].himg = CurObjHImg;
UpdateArray[uArrIndex].ObjectPos.x = CurObjPos.x;
UpdateArray[uArrIndex++].ObjectPos.y = CurObjPos.y;
}
}
#ifdef GATHER_RENDERING_STATS
iMaxObjs = max (iMaxObjs, (int)uArrIndex); // For statistics only!
#endif // GATHER_RENDERING_STATS
//
// Second pass, clear the array
//
if (bImageChanged || m_bRefreshAll)
{
// The game image has changed - m_UpdateRegionRect holds the change rectangle
for (UINT i = 0; i < uArrIndex; i++)
// While there are objects to update in the array
{
// Update back buffer:
m_ImageManager.DisplayImage (UpdateArray[i].himg,
&m_BackBuffer,
UpdateArray[i].ObjectPos);
}
if (m_bRefreshAll)
{ // First time around ?
m_bRefreshAll = FALSE; // No more !
// Update entire map to display all terrain
m_UpdateRegionRect.SetRect (0,0, MAP_WIDTH , MAP_HEIGHT);
}
// Dump it (flip)
DumpBackBufferToScreen ();
}
lSleepTime = LONG(m_uMilliSleep) - LONG(m_Timer.GetLocalTime() - LONG(dwLoopStart));
#ifdef GATHER_RENDERING_STATS
// Update counters, timers and statistics:
dwLoopCount++;
lTotalTimeLeft += lSleepTime; // For statistics only !
lMaxTimeLeft = max (lMaxTimeLeft, lSleepTime); // For statistics only !
lMinTimeLeft = min (lMinTimeLeft, lSleepTime); // For statistics only !
#endif
if (lSleepTime > 1) // Do we need to sleep at all?
Sleep (lSleepTime);
} // End of main rendering loop
#ifdef GATHER_RENDERING_STATS
// Debug performance dump:
TRACE ( "\n\t\t\t**** Rendering statistics ****\n");
TRACE ("%d loops per second\n",
dwLoopCount / ((GetTickCount() - dwStartTime) / 1000));
TRACE ("Avg. time left in loop = %f millisecs. Min = %d millisecs, Max = %d millisecs\n",
double(lTotalTimeLeft) / double(dwLoopCount), lMinTimeLeft, lMaxTimeLeft);
TRACE ("Max concurrent objects = %d\n\n\n", iMaxObjs);
#endif
}
#pragma warning (default : 4701)
/*------------------------------------------------------------------------------
Function: DumpBackBufferToScreen
Purpose: Switched between the back buffer and the front buffer.
Input: None.
Output: None.
Remarks: See game manager algorithm for details.
------------------------------------------------------------------------------*/
void CGameManager::DumpBackBufferToScreen ()
{
HDC dc = ::GetDC (m_MapHWnd);
int iWidth = m_UpdateRegionRect.Width(),
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -