GameManager.cpp
上传用户:royluo
上传日期:2007-01-05
资源大小:1584k
文件大小:37k
源码类别:

游戏

开发平台:

Visual C++

  1. /*****************************************************************************
  2. *                                                                             
  3. *   GameManager.cpp                                                            
  4. *                                                                             
  5. *   Electrical Engineering Faculty - Software Lab                             
  6. *   Spring semester 1998                                                      
  7. *                                                                             
  8. *   Tanks game                                                                
  9. *                                                                             
  10. *   Module description: The core of the game - implements the main game loop.
  11. *                       Holds the list of objects participating in the game.
  12. *                       In every game iteration, all the objects on the list
  13. *                       are refreshed, and the game display is updated.
  14. *                       More details on the algorithms in use are described below.
  15. *                                                                             
  16. *   Authors: Eran Yariv - 28484475                                           
  17. *            Moshe Zur  - 24070856                                           
  18. *                                                                            
  19. *                                                                            
  20. *   Date: 23/09/98                                                           
  21. *                                                                            
  22. ******************************************************************************/
  23. #include "stdafx.h"
  24. #include <afxmt.h>
  25. #include "tanks.h"
  26. #include "DIB.h"
  27. #include "GameManager.h"
  28. #include "ImageManager.h"
  29. #include "MsgQueue.h"
  30. #include "Bonus.h"
  31. #include "GameBoard.h"
  32. #include "TankObj.h"
  33. #include "Shell.h"
  34. #include "Bullet.h"
  35. #include "Mine.h"
  36. #include "Bomber.h"
  37. #include "Message.h"
  38. #include "GameOver.h"
  39. CGameManager::CGameManager (UINT uFreq) :
  40.         m_Timer(TANKS_APP->m_gTimer),
  41.         m_ImageManager(TANKS_APP->m_gImageManager),
  42.         m_MsgQueue(TANKS_APP->m_gIncomingMsgQueue),
  43.         m_CommManager(TANKS_APP->m_gCommManager),
  44.         m_dwBonusTimeout (0)
  45. {
  46.     VERIFY (SetFrequency (uFreq));
  47. }
  48. BOOL CGameManager::SetFrequency (UINT uFreq)
  49. {
  50.     if (uFreq < MIN_RENDER_FREQ || uFreq > MAX_RENDER_FREQ)
  51.         return FALSE;
  52.     m_uFreq = uFreq;
  53.     m_uMilliSleep = UINT (double(1000.0) / double(m_uFreq));
  54.     return TRUE;
  55. }
  56. UINT CGameManager::ThreadEntry (LPVOID /*lpParam*/)
  57. {
  58.     SetPriority (GAME_MANAGER_THREAD_PRIORITY);
  59.     m_MapHWnd = TANKS_APP->m_gHwndMap;              // Get handle to map (Windows handle)
  60. ASSERT (NULL != m_MapHWnd);
  61.     m_dwChkSumSignal = 0;                           // Don't send check sum right away
  62.     m_iNumTanks = 0;                                // Initially, no tanks.
  63.     m_iLocalTankID = -1;                            // Initially, no local tank
  64.     HDC dc = ::GetDC (m_MapHWnd);
  65.     m_BackBuffer.CreateEmpty (MAP_WIDTH, MAP_HEIGHT);// Create back (off-screen) buffer
  66.     m_BackBuffer.GetPaletteFromResourceBitmap (IDB_BULLET);
  67.     TANKS_APP->m_gDrawDIB.SetPalette (*m_BackBuffer.m_pPalette);
  68.     TANKS_APP->m_gDrawDIB.Realize (dc, FALSE);
  69.     ::ReleaseDC (m_MapHWnd, dc);
  70.     MultiRectGameTechnique ();                      // Do the game main loop
  71.     DestroyObjects ();                              // Kill list of objects
  72.     return 0;
  73. }
  74. void CGameManager::DestroyObjects ()
  75. {
  76.     m_GameObjsList.KillList();
  77.     m_TanksCS.Lock();
  78.     for (int i=0; i<MAX_TANKS; i++)
  79.         m_pTanks[i] = NULL;
  80.     m_TanksCS.Unlock();
  81.     m_BonusCS.Lock();
  82.     m_pBonus = NULL;
  83.     m_BonusCS.Unlock();
  84.     m_iNumTanks = 0;
  85. }
  86. #pragma warning (disable : 4701)
  87. /* warning C4701: local variable 'CurObjHImg' may be used without having been initialized
  88.    God damn it, trust me on this one - I know what I'm doing here....
  89. */
  90. /*------------------------------------------------------------------------------
  91.   Function: MultiRectGameTechnique
  92.   Purpose:  Main game loop. Every iteration of the loop, game objects recalc
  93.             their status, and the display is refreshed. Every Iteration lasts a 
  94.             constant period of time, to allow a steady frame rate of the display, 
  95.             using the sleep system call, to stay low on CPU use.
  96.   Input:    None.
  97.   Output:   None.
  98.   Remarks:  The algorithm in use is explained in details in the Programmers's
  99.             manual section in the game's help files.
  100.             
  101. ------------------------------------------------------------------------------*/
  102. void 
  103. CGameManager::MultiRectGameTechnique ()
  104. {
  105. #ifdef GATHER_RENDERING_STATS
  106.     DWORD dwLoopCount=0;
  107.     DWORD dwStartTime = GetTickCount();
  108.     int lTotalTimeLeft = 0,                 // For statistics only !
  109.         lMaxTimeLeft = 0,                   // For statistics only !
  110.         lMinTimeLeft = LONG(m_uMilliSleep), // For statistics only !
  111.         iMaxObjs = 0;                       // For statistics only !
  112. #endif  // GATHER_RENDERING_STATS
  113.     BOOL bImageChanged;
  114.     typedef struct {
  115.         HIMAGE      himg;
  116.         CPoint      ObjectPos;
  117.     } UpdateArrayUnit;
  118.     UpdateArrayUnit UpdateArray[MAX_POSSIBLE_OBJECTS];
  119.     UINT uArrIndex;
  120.     LONG lSleepTime;         // Time left to sleep (may be negative if loop is too slow)
  121.     m_bRefreshAll = TRUE;
  122.         // Initially, empty the message queue to add the board object to the game objects list
  123.     EmptyMsgQ(dwStartTime);
  124.         // First, create the constant board in the back-buffer
  125.     LIST_POS lp = m_GameObjsList.GetHeadPosition();
  126.     CGameObject* pBoard = m_GameObjsList.GetNext(lp);
  127.     ASSERT (pBoard->GetType() == BOARD);
  128.     CDIB *pBoardDIB = m_ImageManager.ExposeDIB (pBoard->GetImage());
  129.         // Copy the board directly - no transparency
  130.     m_BackBuffer.CopyFrom (pBoardDIB);
  131.     while (!m_bEndThread)
  132.     {   // Loop while game lasts
  133.             // Get sample time
  134.         DWORD dwCurTime = m_Timer.GetLocalTime();
  135.         DWORD dwLoopStart = dwCurTime;
  136.         AttemptToSendGameChecksum ();
  137.         if (m_CommManager.IsHost())
  138.             TryToAddBonus (dwCurTime);
  139.             // Empty object list requests queue
  140.         bImageChanged = EmptyMsgQ(dwCurTime);
  141.             // Init update rectangle
  142.         m_UpdateRegionRect.SetRectEmpty();
  143.             // Init updates array
  144.         uArrIndex = 0;
  145.         //
  146.         // First pass: loop the objects 
  147.         //
  148.             // Walk over game objects and update rectangle
  149.             // Skip the board:
  150.         LIST_POS lp = m_GameObjsList.GetHeadPosition();
  151.         CGameObject* pGameObj = m_GameObjsList.GetNext(lp);
  152.         ASSERT (pGameObj->GetType() == BOARD);
  153.             // Loop the other (transparent non-board) objects:
  154.         for ( pGameObj = m_GameObjsList.GetNext(lp);
  155.               pGameObj;
  156.               pGameObj = m_GameObjsList.GetNext(lp) )
  157.         {
  158.             StateType CurObjState = pGameObj->CalcState(dwCurTime);
  159.             HIMAGE CurObjHImg;
  160.                 // Get new position from current object
  161.             CPoint CurObjPos = pGameObj->GetPos();
  162.                 // Get update rectangle from current object
  163.             CRect CurObjUpdateRect = pGameObj -> GetUpdateRectangle();
  164.                 // Update region to be union of all rectangles
  165.             m_UpdateRegionRect |= CurObjUpdateRect;
  166.             if (STATE_DEAD == CurObjState)
  167.             {       // Object is no longer with us:
  168.                 BOOL fIsMine = FALSE;
  169.                 if (pGameObj->GetType() == MINE)
  170.                 {
  171.                     fIsMine = TRUE;
  172.                 }
  173.                 else if (pGameObj->GetType() == TANK)
  174.                 {
  175.                     m_TanksCS.Lock();
  176.                     // A tank is dying, remove it from fast pointer array
  177.                     ASSERT (NULL != m_pTanks[pGameObj->GetID()]);   // Tank must still exist
  178.                     int iID = pGameObj->GetID();
  179.                     if (m_iLocalTankID == iID)
  180.                     {   // Local tank is removed
  181.                         m_CommManager.NotifyExplodingTank(m_iLocalTankID);
  182.                         m_iLocalTankID = -1;
  183.                     }
  184.                     m_pTanks[iID] = NULL;
  185.                     m_iNumTanks --;
  186.                     ASSERT (m_iNumTanks >= 0);
  187.                     m_TanksCS.Unlock();
  188.                 }
  189.                 else if (pGameObj->GetType() == BONUS)
  190.                 {
  191.                     m_BonusCS.Lock();
  192.                     // A bonus is dying, remove it from fast pointer
  193.                     if (m_pBonus == pGameObj)
  194.                         // If the current bonus is dying (not a newer one)
  195.                         m_pBonus = NULL;
  196.                     m_BonusCS.Unlock();
  197.                 }
  198.                 if (fIsMine)
  199.                     m_MinesCS.Lock();
  200.                 m_GameObjsList.RemoveObject(pGameObj);
  201.                 if (fIsMine)
  202.                     m_MinesCS.Unlock();
  203.                     // If object is removed, map image has changed    
  204.                 bImageChanged = TRUE;
  205.             } 
  206.             else 
  207.             {       // STATE_ALIVE: Update update rect size:
  208.                 CurObjHImg = pGameObj->GetImage();  // This call can change the bImageChanged of the object
  209.                 if (!bImageChanged) 
  210.                         // No change detected yet....
  211.                         // Ask the current object if it changed image
  212.                     bImageChanged = pGameObj->HasImageChanged();
  213.             }
  214.                 // Clear back-buffer from previous frame 
  215.             m_BackBuffer.CopyRectFrom (pBoardDIB, 
  216.                                        CurObjUpdateRect.left, 
  217.                                        CurObjUpdateRect.top, 
  218.                                        CurObjUpdateRect.Width(), 
  219.                                        CurObjUpdateRect.Height(), 
  220.                                        CurObjUpdateRect.left, 
  221.                                        CurObjUpdateRect.top);
  222.                 // If the object is still alive, add it to the array:
  223.             if (CurObjState == STATE_ALIVE)
  224.             {
  225.                 ASSERT (uArrIndex < MAX_POSSIBLE_OBJECTS);
  226.                 UpdateArray[uArrIndex].himg = CurObjHImg;
  227.                 UpdateArray[uArrIndex].ObjectPos.x = CurObjPos.x;
  228.                 UpdateArray[uArrIndex++].ObjectPos.y = CurObjPos.y;
  229.             } 
  230.         }
  231.         
  232. #ifdef GATHER_RENDERING_STATS
  233.         iMaxObjs = max (iMaxObjs, (int)uArrIndex);  // For statistics only!
  234. #endif  // GATHER_RENDERING_STATS
  235.         //
  236.         // Second pass, clear the array
  237.         //
  238.         if (bImageChanged || m_bRefreshAll)
  239.         {
  240.             // The game image has changed - m_UpdateRegionRect holds the change rectangle
  241.             for (UINT i = 0; i < uArrIndex; i++)
  242.                 // While there are objects to update in the array
  243.             {
  244.                     // Update back buffer:
  245.                 m_ImageManager.DisplayImage (UpdateArray[i].himg, 
  246.                                              &m_BackBuffer, 
  247.                                              UpdateArray[i].ObjectPos);
  248.             }
  249.             if (m_bRefreshAll) 
  250.             {   // First time around ?
  251.                 m_bRefreshAll = FALSE; // No more !
  252.                     // Update entire map to display all terrain
  253.                 m_UpdateRegionRect.SetRect (0,0, MAP_WIDTH , MAP_HEIGHT);
  254.             }
  255.             // Dump it (flip)
  256.             DumpBackBufferToScreen ();
  257.         }
  258.         lSleepTime = LONG(m_uMilliSleep) - LONG(m_Timer.GetLocalTime() - LONG(dwLoopStart));
  259. #ifdef GATHER_RENDERING_STATS
  260.             // Update counters, timers and statistics:
  261.         dwLoopCount++;
  262.         lTotalTimeLeft += lSleepTime;                    // For statistics only !
  263.         lMaxTimeLeft = max (lMaxTimeLeft, lSleepTime);   // For statistics only !
  264.         lMinTimeLeft = min (lMinTimeLeft, lSleepTime);   // For statistics only !
  265. #endif
  266.         if (lSleepTime > 1)  // Do we need to sleep at all?
  267.             Sleep (lSleepTime);
  268.     }   // End of main rendering loop
  269. #ifdef GATHER_RENDERING_STATS
  270.         // Debug performance dump:
  271.     TRACE ( "nttt**** Rendering statistics ****n");
  272.     TRACE ("%d loops per secondn",
  273.             dwLoopCount / ((GetTickCount() - dwStartTime) / 1000));
  274.     TRACE ("Avg. time left in loop = %f millisecs. Min = %d millisecs, Max = %d millisecsn",
  275.             double(lTotalTimeLeft) / double(dwLoopCount), lMinTimeLeft, lMaxTimeLeft);
  276.     TRACE ("Max concurrent objects = %dnnn", iMaxObjs);
  277. #endif
  278. }
  279. #pragma warning (default : 4701)
  280.     
  281. /*------------------------------------------------------------------------------
  282.   Function: DumpBackBufferToScreen
  283.   Purpose:  Switched between the back buffer and the front buffer.
  284.   Input:    None.
  285.   Output:   None.
  286.   Remarks:  See game manager algorithm for details.
  287. ------------------------------------------------------------------------------*/
  288. void CGameManager::DumpBackBufferToScreen ()
  289. {
  290.     HDC dc = ::GetDC (m_MapHWnd);
  291.     int iWidth  = m_UpdateRegionRect.Width(),
  292.         iHeight = m_UpdateRegionRect.Height();
  293.     m_UpdateRegionRect.top = MAP_HEIGHT - m_UpdateRegionRect.top - iHeight;
  294.     TANKS_APP->m_gDrawDIB.DrawDib ( 
  295.         &m_BackBuffer,                  // Source image
  296.         dc,                             // Device context
  297.         m_UpdateRegionRect.left,        // Destination leftmost corner
  298.         m_UpdateRegionRect.top,         // Destination topmost corner        
  299.         iWidth,                         // Destination width
  300.         iHeight,                        // Destination height
  301.         m_UpdateRegionRect.left,        // Source leftmost corner 
  302.         m_UpdateRegionRect.top,         // Source topmost corner  
  303.         iWidth,                         // Source width
  304.         iHeight,                        // Source height
  305.         0                               // Flags - nothing
  306.     );
  307.     ::ReleaseDC (m_MapHWnd, dc);
  308.     ::ValidateRect (m_MapHWnd, NULL);
  309. }
  310. /*------------------------------------------------------------------------------
  311.   Function: EmptyMsgQ
  312.   Purpose:  Empties the incoming message queue. Messages arrived either internally
  313.             from game objects (e.g. a tank may send add bullet message) or
  314.             externally from the host.
  315.   Input:    dwLoopStartTime: Current loop time (local game time).
  316.   Output:   
  317.   Remarks:   Returns TRUE if any message was processed.
  318. ------------------------------------------------------------------------------*/
  319. BOOL
  320. CGameManager::EmptyMsgQ(DWORD dwLoopStartTime)
  321. {
  322.     CMessage Msg;
  323.     while (m_MsgQueue.Dequeue(Msg))
  324.     {   // While there are still messages to process:
  325.         switch (Msg.GetType())
  326.         {
  327.         //
  328.         // Internal messages (Game objects to game manager)
  329.         //
  330.             case CMessage::ADD_OBJECT:      // Add a general object
  331.                 AddObject ( Msg.m_UnionData.pGameObj );
  332.                 break;
  333.             case CMessage::ADD_GAME_OVER:   // Add game over message
  334.                 AddObject (new CGameOver);
  335.                 break;
  336.             case CMessage::ADD_SHELL:       // Add a shell
  337.                 AddShell (Msg);
  338.                 break;
  339.             case CMessage::ADD_BULLET:      // Add a bullet
  340.                 AddBullet (Msg);
  341.                 break;
  342.             case CMessage::ADD_MINE:        // Add a mine
  343.                 AddMine (Msg);
  344.                 break;
  345.         //
  346.         // Incoming messages (From server, through the comm manager to the game manager)
  347.         //
  348.             case CMessage::ADD_BONUS: 
  349.                 AddBonus (Msg, dwLoopStartTime);
  350.                 break;
  351.             case CMessage::ADD_TANK:
  352.                 AddTank (Msg);
  353.                 break;
  354.             case CMessage::REMOVE_TANK:
  355.                 RemoveTank (Msg);
  356.                 break;
  357.             case CMessage::ADD_BOARD: 
  358.                 AddBoard (Msg);
  359.                 break;
  360.             case CMessage::ADD_BOMBER:
  361.                 AddBomber (Msg, dwLoopStartTime);
  362.                 break;
  363.             case CMessage::SET_TANK_STATUS:
  364.                 SetTankStatus (Msg);
  365.                 break;
  366.             case CMessage::SET_TANK_POS:
  367.                 SetTankPos (Msg);
  368.                 break;
  369.             case CMessage::SET_TANK_ZOMBIE:
  370.                 SetTankZombie (Msg);
  371.                 break;
  372.             default:    // this message isn't for me - bail out:
  373.                 ASSERT(FALSE);
  374.         }
  375.     }
  376.     return TRUE;    // Something always changes
  377. }
  378. /*------------------------------------------------------------------------------
  379.   Function: FindVacantRect
  380.   Purpose:  Returns a postion of a vacant rectangle on the game board.
  381.   Input:    size: Required size of a vacant rectangle.
  382.             pt: Position of vacant rectangle found.
  383.   Output:   None.
  384.   Remarks:  This function must succeed (or else it's caught in an endless loop).
  385.             
  386. ------------------------------------------------------------------------------*/
  387. void
  388. CGameManager::FindVacantRect(CSize& size, CPoint &pt)
  389. {
  390.         // First check if list is empty - means we are positioning the first tank.
  391.         // For that purpose we preserve the (0,0) location on every board.
  392.         // Reason: the fist REQUEST_TANK message may arrive before the game manager 
  393.         // gets the game board.
  394.     if (m_GameObjsList.GetObjectsCount() == 0) {
  395.         pt.x = 0; pt.y = 0;
  396.         return;
  397.     }
  398.     CRect rect;
  399.     do {
  400.         rect.left   = rand() % (MAP_WIDTH - size.cx + 1);
  401.         rect.top    = rand() % (MAP_HEIGHT - size.cy + 1);
  402.         rect.right  = rect.left + size.cx - 1;
  403.         rect.bottom = rect.top + size.cy - 1;
  404.     } while (!IsRectVacant(rect));
  405.     // Got solution
  406.     pt.x = rect.left;
  407.     pt.y = rect.top;
  408. }
  409. /*------------------------------------------------------------------------------
  410.   Function: GetMinDistanceFromTanks
  411.   Purpose:  Returns the minimal distance from the given point to the tanks in game.
  412.             Method is used when placing a new tank on the game board.
  413.   Input:    pt: The position in inspection.
  414.   Output:   The minimal squared distance to the tanks in game, or MAX_DWORD if no
  415.             tanks is playing.
  416. ------------------------------------------------------------------------------*/
  417. DWORD
  418. CGameManager::GetMinDistanceFromTanks (CPoint &pt)
  419. {
  420.     if (0 == m_GameObjsList.GetObjectsCount())
  421.         return MAX_DWORD;   // No objects
  422.     DWORD dwMinDist = MAX_DWORD;
  423.     m_TanksCS.Lock();
  424.     for (int i=0; i<MAX_TANKS; i++)
  425.         if (NULL != m_pTanks[i])
  426.         {   // Found a tank
  427.             CPoint &ptTank = m_pTanks[i]->GetPos();
  428.             DWORD dwCurDist = (ptTank.x - pt.x) * (ptTank.x - pt.x) +
  429.                               (ptTank.y - pt.y) * (ptTank.y - pt.y);
  430.             dwMinDist = min (dwMinDist, dwCurDist);
  431.         }
  432.     m_TanksCS.Unlock();
  433.     return dwMinDist;
  434. }
  435. /*------------------------------------------------------------------------------
  436.   Function: IsRectVacant
  437.   Purpose:  Check that the given game board rectangle is clear of game object.
  438.   Input:    rect: The rectangle in inspection.
  439.   Output:   Return TRUE if rectangle is vacant.
  440. ------------------------------------------------------------------------------*/
  441. BOOL
  442. CGameManager::IsRectVacant(CRect& rect)
  443. {
  444.     LIST_POS lp = m_GameObjsList.GetHeadPosition();
  445.     // Make sure list is untouched during search:
  446.     m_GameObjsList.Freeze();
  447.     // First, check if board has that rectangle vacant:
  448.     CGameObject* pGameObj = m_GameObjsList.GetNext(lp);  // GameBoard
  449.     ASSERT(BOARD == pGameObj->GetType());
  450.     if (! ((CGameBoard*)pGameObj)->IsRectVacant(rect) ) {
  451.         m_GameObjsList.Thaw();
  452.         return FALSE;
  453.     }
  454.     CRect TstRect;
  455.     // Now, check with all other objects in LOWER_LEVEL:
  456.     for ( pGameObj = m_GameObjsList.GetNext(lp);
  457.           pGameObj && HIGHER_LEVEL > pGameObj->GetHeight();
  458.           pGameObj = m_GameObjsList.GetNext(lp) ) {
  459.               // On the first intersection - stop and exit:
  460.             if ( TstRect.IntersectRect(pGameObj->CGameObject::GetUpdateRectangle(), rect) )
  461.             {
  462.                 m_GameObjsList.Thaw();
  463.                 return FALSE;
  464.             }
  465.     }
  466.     m_GameObjsList.Thaw();
  467.     return TRUE;
  468. }
  469. /*------------------------------------------------------------------------------
  470.   Function: TryToAddBonus
  471.   Purpose:  Attempts to add a new bonus object to the game.
  472.   Input:    dwCurTime: Current game time.
  473.   Output:   None.
  474.   Remarks:  A new bonus is added iff the former one has expired. This way we 
  475.             ensure only one bonus is available at a time.
  476. ------------------------------------------------------------------------------*/
  477. void 
  478. CGameManager::TryToAddBonus (DWORD dwCurTime)
  479. {
  480.     if (dwCurTime < m_dwBonusTimeout)
  481.         return;     // Not time yet
  482.     ASSERT (m_CommManager.IsHost());    // Non-host machines don't create bonuses
  483.     // Pick bonus timeout
  484.     DWORD ttl    = BONUS_MIN_LIFESPAN + BONUS_LIFESPAN_RANGE * (rand () % 5);  // sleep between 1~3 min. in 30 sec. jumps.
  485.     // Pick bonus type
  486.     BonusType bt = BonusType(1 + (rand() % BONUS_SHIELD));
  487.     // Pick bonus location
  488.     CPoint loc;
  489.     
  490.     FindVacantRect (CSize (BONUS_WIDTH, BONUS_HEIGHT) ,loc);
  491.     // Post a new bonus message:
  492.     m_CommManager.NotifyNewBonus (bt, ttl, loc);
  493.     // Calc time in the future to spawn a new bonus
  494.     m_dwBonusTimeout = dwCurTime + ttl + 2000;
  495. }
  496. /*------------------------------------------------------------------------------
  497.   Function: AttemptToSendGameChecksum
  498.   Purpose:  Sends a check sum representing our local player's game status.
  499.             The host compares the check sum with it's own game status, and
  500.             send updates on mismatches found.
  501.   Input:    None.
  502.   Output:   None.
  503.   Remarks:  The method is called every loop iteration, but execution is completed
  504.             only once every CHKSUM_TIME_GAP_BITS ms.
  505. ------------------------------------------------------------------------------*/
  506. void
  507. CGameManager::AttemptToSendGameChecksum ()
  508. {   // This function must be called only from within the gamer manager game loop
  509.     if ((m_Timer.GetLocalTime () >> CHKSUM_TIME_GAP_BITS) <= m_dwChkSumSignal)
  510.         // There's still time
  511.         return;
  512.     if ((-1 == m_iLocalTankID) &&       // No local tank. The game is in GameOver mode.
  513.         !m_CommManager.IsHost())        // Not a judge
  514.         // Game will be over when the user presses any key => send nothing!
  515.         return;
  516.     if (0 == m_dwChkSumSignal)
  517.     {   // First call
  518.         m_dwChkSumSignal = m_Timer.GetLocalTime () >> CHKSUM_TIME_GAP_BITS;
  519.         return;
  520.     }
  521.     m_dwChkSumSignal++; // The checksum for this time-slot is starting
  522.     // Time to check-sum and send
  523.     // Compose checksum message
  524.     CMessage::MessageData msg;
  525.         // Clear all tanks checks sums
  526.     memset (msg.CheckSumParam.TanksChkSums,
  527.             0,
  528.             sizeof (CheckSumParamTag::TankCheckSumParamTag) * MAX_TANKS);
  529.     m_TanksCS.Lock();
  530.     // Get local tanks position:
  531.     if (m_iLocalTankID >= 0)
  532.     {   // Local tank is alive
  533.         CPoint pos = m_pTanks[m_iLocalTankID]->GetPos();
  534.         msg.CheckSumParam.XPos = WORD(pos.x);
  535.         msg.CheckSumParam.YPos = WORD(pos.y);
  536.         msg.CheckSumParam.DeadHostReport = FALSE;
  537.     }
  538.     else
  539.     {   // Reporting tank is host and host is dead
  540.         msg.CheckSumParam.DeadHostReport = TRUE;  // Indicate dead host report
  541.     }
  542.     // Get other info on all tanks, inc. local:
  543.     for (int i=0; i<MAX_TANKS; i++)
  544.         if (m_pTanks[i] != NULL)
  545.         {   // i'th tank exists
  546.             msg.CheckSumParam.TanksChkSums[i].TankExists = TRUE;
  547.             msg.CheckSumParam.TanksChkSums[i].FastFire = m_pTanks[i]->GetFastFire();
  548.             msg.CheckSumParam.TanksChkSums[i].Bullets = WORD(m_pTanks[i]->GetBulletsCount());
  549.             msg.CheckSumParam.TanksChkSums[i].Shells = WORD(m_pTanks[i]->GetShellsCount());
  550.             msg.CheckSumParam.TanksChkSums[i].Mines = WORD(m_pTanks[i]->GetMinesCount());
  551.             msg.CheckSumParam.TanksChkSums[i].Zombie = m_pTanks[i]->IsZombie();
  552.         }
  553.     m_TanksCS.Unlock();
  554.     msg.CheckSumParam.NumberOfTanks = BYTE(m_iNumTanks);
  555.     // Now, the mines:
  556.         // Clear all partial sector checksums:
  557.     memset (msg.CheckSumParam.MinesSectorsChkSum, 
  558.             0,
  559.             sizeof (BYTE) * (MAX_SECTOR + 1));
  560.     
  561.         // Start scanning the list of objects.
  562.     m_MinesCS.Lock();
  563.     LIST_POS lp = m_GameObjsList.GetHeadPosition();
  564.     CGameObject* pGameObj = m_GameObjsList.GetNext(lp);  // GameBoard
  565.     ASSERT(BOARD == pGameObj->GetType());
  566.         // Continue (after the board) and scan for mines (only lower level):
  567.     for ( pGameObj = m_GameObjsList.GetNext(lp);
  568.           pGameObj && (HIGHER_LEVEL > pGameObj->GetHeight());
  569.           pGameObj = m_GameObjsList.GetNext(lp) ) 
  570.     {
  571.         // This is a lower level object
  572.         if (pGameObj->GetType() != MINE)
  573.             continue;
  574.         // This is a mine - nothing can stop us now.
  575.         BYTE bSector = pGameObj->GetSector();
  576.         ASSERT (bSector <= MAX_SECTOR);
  577.         msg.CheckSumParam.MinesSectorsChkSum[bSector] = 
  578.             BYTE(msg.CheckSumParam.MinesSectorsChkSum[bSector] + pGameObj->GetPosCheckSum());
  579.     }
  580.     m_MinesCS.Unlock();
  581.     // Finally, do the bonus flag:
  582.     m_BonusCS.Lock();
  583.     if (m_pBonus != NULL)
  584.         msg.CheckSumParam.ActiveBonusType = m_pBonus->GetBonusType();
  585.     else 
  586.         msg.CheckSumParam.ActiveBonusType = BONUS_NONE;
  587.     m_BonusCS.Unlock();
  588.     // Send it all through the comm. manager
  589.     m_CommManager.NotifyCheckSum (msg);
  590. }
  591. void
  592. CGameManager::GetBonusState(CMessage::MessageData& MsgData)
  593. {
  594.     m_BonusCS.Lock();
  595.     if (m_pBonus) 
  596.     {   // Bonus exists
  597.         MsgData.BonusParam.Type = m_pBonus->GetBonusType();
  598.         CPoint pos(m_pBonus->GetPos());
  599.         MsgData.BonusParam.XPos = pos.x;
  600.         MsgData.BonusParam.YPos = pos.y;
  601.         MsgData.BonusParam.LifeSpan = BONUS_MIN_LIFESPAN + BONUS_LIFESPAN_RANGE;   // Max life span
  602.     }
  603.     else
  604.     {   // Bonus doesn't exist
  605.         MsgData.BonusParam.Type = BONUS_NONE;
  606.         MsgData.BonusParam.LifeSpan = MAX_TANKS;    // No tank has eaten the last bonus
  607.     }
  608.     m_BonusCS.Unlock();
  609. }
  610. BOOL 
  611. CGameManager::GetTankStatus(int iTankID, CMessage::MessageData& MsgData)
  612. {
  613.     BOOL bRes = FALSE;
  614.     m_BonusCS.Lock();
  615.     ASSERT(iTankID < MAX_TANKS);
  616.     if (m_pTanks[iTankID]) {
  617.         bRes = TRUE;
  618.         m_pTanks[iTankID]->GetStatus(MsgData);
  619.     }
  620.     m_BonusCS.Unlock();
  621.     return bRes;
  622. }
  623. BOOL 
  624. CGameManager::GetTankStatusAndPos(int iTankID, CMessage::MessageData& MsgData)
  625. {
  626.     BOOL bRes = FALSE;
  627.     m_BonusCS.Lock();
  628.     ASSERT(iTankID < MAX_TANKS);
  629.     if (m_pTanks[iTankID]) {
  630.         bRes = TRUE;
  631.         m_pTanks[iTankID]->GetStatusAndPos(MsgData);
  632.     }
  633.     m_BonusCS.Unlock();
  634.     return bRes;
  635. }
  636. DWORD
  637. CGameManager::GetMinesInSector (UINT uSector, DWORD *pAllMinesInSector)
  638. {
  639.     DWORD dwCount = 0;
  640.     m_MinesCS.Lock();
  641.     LIST_POS lp = m_GameObjsList.GetHeadPosition();
  642.     CGameObject* pGameObj = m_GameObjsList.GetNext(lp);  // GameBoard
  643.     ASSERT(BOARD == pGameObj->GetType());
  644.         // Continue (after the board) and scan for mines (only lower level):
  645.     for ( pGameObj = m_GameObjsList.GetNext(lp);
  646.           pGameObj && (HIGHER_LEVEL > pGameObj->GetHeight());
  647.           pGameObj = m_GameObjsList.GetNext(lp) ) 
  648.     {
  649.         // This is a lower level object
  650.         if (pGameObj->GetType() != MINE)
  651.             continue;
  652.         // This is a mine - check for sector
  653.         if (pGameObj->GetSector() != uSector)
  654.             continue;   // Mine is not of requested sector
  655.         // This is a mine of requested sector - add it
  656.         
  657.         pAllMinesInSector[dwCount++] = MAKELONG (pGameObj->GetPos().x, pGameObj->GetPos().y);
  658.     }
  659.     m_MinesCS.Unlock();
  660.     return dwCount;
  661. }
  662. void
  663. CGameManager::SetMinesInSector (UINT uSector, DWORD dwNumMines, DWORD *pAllMinesInSector)
  664. {
  665.     m_MinesCS.Lock();
  666.     LIST_POS lp = m_GameObjsList.GetHeadPosition();
  667.     CGameObject* pGameObj = m_GameObjsList.GetNext(lp);  // GameBoard
  668.     ASSERT(BOARD == pGameObj->GetType());
  669.     DWORD dwNewMines = dwNumMines;
  670.         // Continue (after the board) and scan for mines (only lower level):
  671.     for ( pGameObj = m_GameObjsList.GetNext(lp);
  672.           pGameObj && (HIGHER_LEVEL > pGameObj->GetHeight());
  673.           pGameObj = m_GameObjsList.GetNext(lp) ) 
  674.     {
  675.         // This is a lower level object
  676.         if (pGameObj->GetType() != MINE)
  677.             continue;
  678.         // This is a mine - check for sector
  679.         if (pGameObj->GetSector() != uSector)
  680.             continue;   // Mine is not of requested sector
  681.         // This is a mine of requested sector - find it in the list
  682.         BOOL bMineFound = FALSE;
  683.         for (DWORD i=0; i < dwNumMines; i++)
  684.             if ((pGameObj->GetPos().x == LOWORD (pAllMinesInSector[i])) &&
  685.                 (pGameObj->GetPos().y == HIWORD (pAllMinesInSector[i])))
  686.             {
  687.                 // The mine is found in the list:
  688.                 pAllMinesInSector[i] = MAX_DWORD;   // Mark it out
  689.                 dwNewMines--;                       // This is not a new mine
  690.                 bMineFound = TRUE;
  691.                 break;                              // Stop searching the list
  692.             }
  693.         if (!bMineFound)
  694.         {
  695.             // Mine is not in the list but is in the game - Kill it
  696.             pGameObj->Kill();
  697.         }
  698.     }
  699.     m_MinesCS.Unlock();
  700.     // Now the list contains exactly dwNewMines new mines to be added.
  701.     if (0 == dwNewMines)
  702.         return; // Quit now if there are no new mines to add
  703.     for (DWORD i=0; i < dwNumMines; i++)
  704.         if (pAllMinesInSector[i] != MAX_DWORD)
  705.         {   // Mine is in the list and not marked => Add it using a message to the incoming queue
  706.             CMessage::MessageData Params;
  707.             Params.MineParam.wXPos = LOWORD (pAllMinesInSector[i]);
  708.             Params.MineParam.wYPos = HIWORD (pAllMinesInSector[i]);
  709.             VERIFY (m_MsgQueue.Enqueue(CMessage::ADD_MINE, Params));
  710.             if (--dwNewMines == 0)
  711.                 // No more new mines, stop scanning the list and exit
  712.                 break;
  713.         }
  714. }
  715. // Message handlers:
  716. void 
  717. CGameManager::AddBonus (CMessage &Msg, DWORD dwLoopStartTime)
  718. {
  719.     if (BONUS_NONE == Msg.m_UnionData.BonusParam.Type)
  720.     { // Bonus is being removed - Tank ID is in LifeSpan (MAX_TANKS for no tank)
  721.         if (Msg.m_UnionData.BonusParam.LifeSpan < MAX_TANKS)
  722.         {   // Some tank ate the bonus, tell him about that
  723.             m_TanksCS.Lock();
  724.             if (m_pTanks[Msg.m_UnionData.BonusParam.LifeSpan] != NULL)
  725.                 // This tank is alive on this machine - feed the bonus to the tank
  726.                 m_pTanks[Msg.m_UnionData.BonusParam.LifeSpan]->EatBonus (m_LastBonusType, 
  727.                                                                                 Msg.GetTime());
  728.             m_TanksCS.Unlock();
  729.         }
  730.         m_BonusCS.Lock();
  731.         if (NULL != m_pBonus)
  732.         {   // Bonus didn't timeout yet
  733.             m_pBonus->Kill();   // Remove this bonus object
  734.             m_pBonus = NULL;    // Deref (make room for new bonus right away)    
  735.         }
  736.         m_BonusCS.Unlock();
  737.     }
  738.     else
  739.     { // Bonus is being added
  740.         m_BonusCS.Lock();
  741.         if (NULL != m_pBonus)
  742.             // Make sure there's only one bonus
  743.             m_pBonus->Kill();   // Remove this bonus object
  744.         m_pBonus = new CBonus (
  745.             BonusType(Msg.m_UnionData.BonusParam.Type),
  746.             CPoint (Msg.m_UnionData.BonusParam.XPos, 
  747.                     Msg.m_UnionData.BonusParam.YPos),
  748.             1000L * Msg.m_UnionData.BonusParam.LifeSpan, // Convert from secs to msecs.
  749.             dwLoopStartTime);
  750.         AddObject (m_pBonus);
  751.         m_LastBonusType = BonusType(Msg.m_UnionData.BonusParam.Type);
  752.         m_BonusCS.Unlock();
  753.     }
  754. }
  755. void
  756. CGameManager::AddTank (CMessage &Msg)
  757. {
  758.     m_TanksCS.Lock();
  759.     // The following assertion was removed:
  760.     // Reason:  If this is a client (non-judge) and it connects to a server,
  761.     //          the server sends ADD_TANK after the client first check sum report.
  762.     //          The ADD_TANK may not be fully digested until it's time to send
  763.     //          another checksum report, which will report the tank as still missing
  764.     //          and will cause another ADD_TANK with the same tank ID.
  765. //            ASSERT (NULL == m_pTanks[Msg.m_UnionData.TankParam.ID]);   // Can't add same tank twice
  766.     if (NULL == m_pTanks[Msg.m_UnionData.TankParam.ID])
  767.     {   // First time this tank is seen
  768.         m_pTanks[Msg.m_UnionData.TankParam.ID] = new CTankObj (
  769.             Msg.m_UnionData.TankParam.ID,
  770.             Msg.m_UnionData.TankParam.XPos,
  771.             Msg.m_UnionData.TankParam.YPos,
  772.             Msg.m_UnionData.TankParam.Direction,
  773.             Msg.m_UnionData.TankParam.Local,
  774.             Msg.m_UnionData.TankParam.ShieldLevel,
  775.             Msg.m_UnionData.TankParam.Shells,
  776.             Msg.m_UnionData.TankParam.Bullets,
  777.             Msg.m_UnionData.TankParam.Mines,
  778.             Msg.m_UnionData.TankParam.FireRateBonusSecsLeft);  // @@ fix last parameter @@@
  779.         AddObject (m_pTanks[Msg.m_UnionData.TankParam.ID]);
  780.         m_iNumTanks++;
  781.         ASSERT (m_iNumTanks <= MAX_TANKS);
  782.     }
  783.     else
  784.     {   
  785.         //
  786.         // An already existing tank was added. Ignore, next checksum will fix tank properties
  787.         //
  788.     }
  789.     // If this our local tank - attach the keyboard to the manouver set:
  790.     if (Msg.m_UnionData.TankParam.Local)
  791.     {   // Local tank
  792.         TANKS_APP->m_gKbdManager.SetManouverSet 
  793.             (&(TANKS_APP->m_gManouverSets[Msg.m_UnionData.TankParam.ID]));
  794.         m_iLocalTankID = Msg.m_UnionData.TankParam.ID;
  795.     }
  796.     else
  797.     {   // Adding a remote tank, make sure the manouver set is clear
  798.         TANKS_APP->m_gManouverSets[Msg.m_UnionData.TankParam.ID].Clear();
  799.     }
  800.     m_TanksCS.Unlock();
  801. }
  802. void
  803. CGameManager::RemoveTank (CMessage &Msg)
  804. {
  805.     m_TanksCS.Lock();
  806.     CTankObj *pTank = m_pTanks[Msg.m_UnionData.TankRemoveParam.ID];
  807.     if (pTank &&                    // If tank exists and
  808.         !pTank->IsExploding())      // it is not already exploding
  809.     {
  810.         pTank->Kill();
  811.     }
  812.     m_TanksCS.Unlock();
  813. }
  814. void
  815. CGameManager::AddBoard (CMessage &Msg)
  816. {
  817.     CGameBoard *pGameBoard = new CGameBoard ();
  818.     pGameBoard->GenerateBoard (
  819.         Msg.m_UnionData.BoardParam.Seed,
  820.         Msg.m_UnionData.BoardParam.Complexity);
  821.     AddObject (pGameBoard);
  822. }
  823. void
  824. CGameManager::AddShell (CMessage &Msg)
  825. {
  826.     AddObject(new CShell (
  827.         Msg.m_UnionData.ShellParam.wXPos,
  828.         Msg.m_UnionData.ShellParam.wYPos,
  829.         Msg.m_UnionData.ShellParam.bDirectionIndex,
  830.         Msg.m_UnionData.ShellParam.bParentTankID));
  831. }
  832. void
  833. CGameManager::AddBullet (CMessage &Msg)
  834. {
  835.     AddObject (new CBullet (
  836.         Msg.m_UnionData.BulletParam.wXPos,
  837.         Msg.m_UnionData.BulletParam.wYPos,
  838.         Msg.m_UnionData.BulletParam.bDirectionIndex,
  839.         Msg.m_UnionData.BulletParam.bParentTankID));
  840. }
  841. void
  842. CGameManager::AddBomber (CMessage &Msg, DWORD dwLoopStartTime)
  843. {
  844.     // Create and add a new flying bomber
  845.     AddObject (new CBomber (Msg.m_UnionData.BomberParam.Direction,
  846.                             Msg.m_UnionData.BomberParam.Xpos,
  847.                             Msg.m_UnionData.BomberParam.Ypos,
  848.                             dwLoopStartTime));
  849. }
  850. void
  851. CGameManager::AddMine (CMessage &Msg)
  852. {
  853.     m_MinesCS.Lock();
  854.     AddObject (new CMine (Msg.m_UnionData.MineParam.wXPos,
  855.                           Msg.m_UnionData.MineParam.wYPos));
  856.     m_MinesCS.Unlock();
  857. }
  858. void
  859. CGameManager::SetTankStatus (CMessage &Msg)
  860. {
  861.     ASSERT(Msg.m_UnionData.TankStatusParam.bID < MAX_TANKS);
  862.     if (m_pTanks[Msg.m_UnionData.TankStatusParam.bID])
  863.     {   // Tank exists:
  864.         // Set its status:
  865.         m_pTanks[Msg.m_UnionData.TankStatusParam.bID]->SetStatus(Msg.m_UnionData);
  866.     }
  867. }
  868. void
  869. CGameManager::SetTankPos (CMessage &Msg)
  870. {
  871.     ASSERT(Msg.m_UnionData.TankPosParam.ID < MAX_TANKS);
  872.     if (m_pTanks[Msg.m_UnionData.TankPosParam.ID])
  873.     {   // Tank exists:
  874.         // TODO: The following assert fails, that the reason it's marked out:
  875.         //ASSERT (Msg.m_UnionData.TankPosParam.ID != UINT(m_iLocalTankID)); 
  876.         // This msg shouldn't be send back to sender
  877.         if (Msg.m_UnionData.TankPosParam.ID == UINT(m_iLocalTankID))
  878.             return;
  879.         m_pTanks[Msg.m_UnionData.TankPosParam.ID]->
  880.             SetPos (Msg.GetTime(),
  881.                     Msg.m_UnionData.TankPosParam.XPos,  // Set its position
  882.                     Msg.m_UnionData.TankPosParam.YPos); // according the time stamp
  883.     }
  884. }
  885. void
  886. CGameManager::SetTankZombie (CMessage &Msg)
  887. {
  888.     ASSERT(Msg.m_UnionData.TankZombieParam.ID < MAX_TANKS);
  889.     if (m_pTanks[Msg.m_UnionData.TankZombieParam.ID])  // Tank exists:
  890.         m_pTanks[Msg.m_UnionData.TankZombieParam.ID]->
  891.             SetZombie (Msg.m_UnionData.TankZombieParam.Zombie);
  892. }