Home
   Home  |  Preliminary Docs  |  Design Diagrams  |  Reference  |  Development Docs  |  Download  |  Source Code  |  Final Reports  |
Usage of State Machines in Solitaire Pack

The Solitaire Pack engine (implemented in the CEngine class) keeps track of two states: one for the engine in general and one for the state of the mouse.

Engine State

The engine state dictates what actions can be performed at the current time. The following are the engine state values:

State Value Description
ENG_UNINIT 0 The engine has been created but not yet initialized
ENG_INITTING 1 The engine is being initialized
ENG_INACTIVE 2 The engine has been initialized but there is no current game (that is, the game menu is probably being displayed)
ENG_STARTGAME 3 A game is being initialized (its ISolpackGame::Initialize is being called)
ENG_STOPGAME 4 A game is being uninitialized (its ISolpackGame::Uninitialize is being called)
ENG_IDLE 5 A game is loaded; the engine is waiting for a user action to react to
ENG_PILECLICKED 6 In response to the user clicking on a pile, the game's ISolpackGame::PileClicked is being called
ENG_SETUPFRAG 7 In response to the user attempting to initiate a drag operation, the game's ISolpackGame::SetupFragment is being called
ENG_DROPFRAG 8 The user released a fragment that was being dragged; the engine is calling the game's ISolpackGame::DropFragment to find a potential drop target
ENG_HANDLECOMMAND 9 In response to the user clicking a custom game command, the game's ISolpackGame::HandleCommand is being called
ENG_DEAL 10 The game's ISolpackGame::Deal is being processed
ENG_AUTOPLAY 11 An autoplay function (ISolpackGame::AutoPlay or AutoPlayCard) is being processed
ENG_GAMEWON 12 The user won the game but has not yet dealt a new game
ENG_MOUSEDOWN 0x20000000 Mouse actions are taking place, such as a drag operation
ENG_ANIM 0x40000000 One or more cards are currently being animated
ENG_UNDO 0x80000000 An undo or redo operation is being executed

The final three entries are not separate states, but are flags that are or'd with the current state. A macro, ENGSTATE, returns the state stripped of any of these flags that may be present.

The engine state is not managed by a centralized state machine control; however, the following diagram shows what valid transitions exist between states (excluding the flag states):

Keeping track of engine state is important to controlling what actions can take place at what time. For example, the only time that game objects can create piles (via ISolpackApp::CreatePile) is from their Initialize function. Thus, the CEngine implementation of CreatePile begins with the following statement:

   if(ENGSTATE != ENG_STARTGAME)
      return E_ACCESSDENIED;

Thus, any attempt that a game object makes to create a pile during, say, a DropFragment event, will fail.

Most of the engine states correspond to a ISolpackGame method that is being processed. Thus, if the game's implementation of one of the methods makes a call back to the engine object, the engine has an easy way to check to see if the call can be processed.

The ENG_UNDO flag is important in controlling when user actions are recorded on the undo stack and when they are not. When the engine has to carry out a user action (such as turning over a card), it puts all data for the user action into a CAction object. The engine then carries out the action by calling whatever code actually performs the action. Then, if ENG_UNDO is not present in the engine state, the undo manager records the action in the undo stack. The presence of ENG_UNDO would signify that the processing of the action is in response to the Redo command rather than the initial time the user performs the action; thus, the action would not need to be recorded.

Overall, keeping track of engine state is very useful. It provides a quick way to, using a single value, check what state the engine is in and what can be performed. The old implementation of Solitaire Pack used a number of boolean variables and other means to keep track of individual things to check for, such as has the user interface been loaded fully or whether the engine is currently calling the game's ISolpackGame::Initialize method. Checking for a valid state before processing an action would require checking a number of different variables; as such, the previous version did very little checking for valid processing. Using engine state is a significant improvement in this version of Solitaire Pack.

Mouse State

Much of the Solitaire Pack engine's logic revolves around processing user mouse actions. For instance, the game's ISolpackGame::PileClicked method is called when the engine receives a left mouse button down event on a pile or card, followed by a left mouse button up event. Other mouse events include moving the mouse and clicking with different buttons.

Before doing any programming, and without looking at any of the mouse event handling code from my previous implementation, I analyzed which mouse events would cause which game actions. Using Rational Rose, I developed a state chart diagram detailing the various mouse states and how mouse events influence what action the user is performing.

State Value Description
MS_IDLE 0 There are no current mouse events; no buttons are pressed.
MS_WAITALL 1 One or more buttons are down, but there are no potential actions until the user releases buttons and tries again.
MS_PILE_LBD 2 The left button is pressed down over a Pile object (not a card).
MS_PILE_CLICKED 3 The left mouse button was released over a pile (or nondraggable card) and the game's ISolpackGame::PileClicked is being processed.
MS_CARD_LBD 4 The left button is pressed down over a Card object, but the mouse has not yet been moved.
MS_DRAGGING 5 The left button is pressed down and one or more cards are being dragged as the mouse moves.
MS_CARD_LBDNODRAG 6 The left button is pressed down; when the user moved the mouse, the game object's ISolpackGame::SetupFragment did not specify any cards to be dragged. Can become a valid PileClicked if no other buttons are clicked.
MS_TRYDROP 7 The engine is querying the pile(s) that the dragged cards were released over to see if any of them accept the cards.
MS_CARD_RBD 8 The right button is pressed down over a Pile object (not a card).
MS_CARD_AUTOPLAY 9 An autoplay command is being processed in response to the right button being released over a card.

Before continuing, I will give a brief explanation of Solitaire Pack's event handling mechanism.

The entire Solitaire Pack interface is implemented with Internet Explorer components (the MSHTML renderer); card and pile objects are displayed onscreen as HTML Span elements. MSHTML allows programs to "handle" events, such as the mouse button being pressed or released on an element.

Because event handlers are used so commonly in many of my programs, I wrote a generic CEventHandler class that encapsulates generic event handler capabilities. The CCard and CPile classes, which create and manage the MSHTML element objects, create and bind event handlers to the following events:

  • onmousedown
  • onmouseup
  • onmousemove

So, for instance, when the mouse is pressed down over a card element, the card's onmousedown event handler is notified. In all cases, the CCard and CPile classes forward all mouse events to the engine (CEngine), via the following CEngine functions:

  • MouseDown
  • MouseUp
  • MouseMove

The MouseDown and MouseUp functions are also passed information about what button was pressed or released. The particular event, combined with button information, make up the "transition" between mouse states; these functions use the current state and this "transition" to determine the new mouse state, and what action, if any, to perform.

For the most part, each of the functions use a switch statement to look at the current state and then assign a new state, and optionally perform tasks. The following code fragments, although simplified slightly, convey the ease with which mouse events are processed.

//----------------------------------------------------------------------------
//
// IEngineObjSite::MouseDown method
//    Called when a mouse button has been pressed on a pile or card.
//
// Parameters:
//    pObj:          The pile or card which was clicked.
//    lCurButton:    Value specifiying which mouse buttons are down.
//
// Returns:          TRUE if the object should capture focus, else FALSE.
//
//----------------------------------------------------------------------------
BOOL CEngine::MouseDown(CEngineObj *pObj, long lCurButton)
{
   // Notes:
   //  1. m_lButton is a member variable which tracks which buttons
   //     are down between events.
   //  2. Values: Buttons are a combination of the following values:
   //         0: no button
   //         1: left button
   //         2: right button
   //         3: middle button
   //  3. SETMS sets the mouse state; SetState sets the engine state.

   // Update engine state
   SetState(m_dwState | ENG_MOUSEDOWN);

   // Figure out which button was just pressed down
   long lNewButton = lCurButton - m_lButton;
   m_lButton = lCurButton;

   // If the engine is not idle, don't do anything
   if(ENGSTATE != ENG_IDLE)
   {
      SETMS(MS_WAITALL);
      return TRUE;
   }

   // First: handle if it is a pile
   //  (code omitted)

   // Otherwise, it is a card.
   // Look at current state, and decide on new state.
   switch(m_dwMouseState)
   {
      case MS_IDLE:
         m_pCardSrc = (CCard*)pObj;
         m_pPileSrc = (CPile*)(m_pCardSrc->GetPile());

         // left clicked?
         if(lNewButton == 1)
         {
            // Set new state
            SETMS(MS_CARD_LBD);

            // Track what coordinate the mouse is at
            CHTMLEventObjPtr spEvent = m_pHH->GetEvent();
            CHECKHRCOM(spEvent->get_clientX(&m_ptDrag.x));
            CHECKHRCOM(spEvent->get_clientY(&m_ptDrag.y));
         } else
         
         // right clicked?
         if(lNewButton == 2)
         {
            // Set new state
            SETMS(MS_CARD_RBD);
         } else

         // anything else--wait for user to release all buttons
         {
            SETMS(MS_WAITALL);
         }
         return TRUE;
         break;

      // Clicking while in any of these states makes us wait for all
      // buttons to be released before continuing
      case MS_PILE_LBD:
      case MS_CARD_LBD:
      case MS_CARD_LBDNODRAG:
      case MS_CARD_RBD:
         SETMS(MS_WAITALL);
         break;

      // All other states not listed (MS_WAITALL, MS_DRAGGING) have
      // transitions to themselves after a button click. Hence they are
      // not listed.
   }
   return FALSE;
}
//----------------------------------------------------------------------------
//
// IEngineObjSite::MouseUp method
//    Called when a mouse button has been released.
//
// Parameters:
//    bOverEl:       Whether the mouse is being released over the same element
//                   as that which has the focus.
//    lButton:       Value specifiying which mouse button was released.
//
// Returns:          TRUE if the object should release mouse focus, else
//                   FALSE.
//
//----------------------------------------------------------------------------
BOOL CEngine::MouseUp(BOOL bOverEl, long lButton)
{
   // Update stored button value
   m_lButton &= ~lButton;

   // If we were animating, a click cancels the animation (lets the user bypass
   // lengthy animation)
   if(m_dwState & ENG_ANIM)
      m_pFragment->SetCancelAnim(TRUE);

   // was left button released?
   if(lButton == 1)
   {
      switch(m_dwMouseState)
      {
         // In each of these first three cases, the net effect is that the pile/card was clicked.
         case MS_PILE_LBD:
         case MS_CARD_LBD:
         case MS_CARD_LBDNODRAG:
            if(bOverEl)
            {
               // Update the mouse state and engine state, and handle the event
               SETMS(MS_PILE_CLICKED);
               SetState((m_dwState & ENG_FLAGS) | ENG_PILECLICKED);
               HandlePileClicked((WPARAM)m_pPileSrc, (LPARAM)m_pCardSrc);
            }

            // After processing event, set back to idle.
            SETMS(MS_IDLE);
            break;

         // We were dragging, so try to find a drop target
         case MS_DRAGGING:
            // Update mouse state and engine state
            SETMS(MS_TRYDROP);
            SetState((m_dwState & ENG_FLAGS) | ENG_DROPFRAG);
            HandleTryDrop((WPARAM)m_pPileSrc, (LPARAM)m_pCardSrc);

            // wait for all other mouse buttons, if any, to be released before continuing
            SETMS(MS_WAITALL);
            break;
      }
   } else 
      // process right button release
      (code omitted)

   // Before returning from the function:
   if(m_dwMouseState == MS_WAITALL)
   {
      // Check to see if the Waitall has been completed
      // (That is, if all other buttons are released)
      if(m_lButton == 0)
         // If so, set mouse state back to idle.
         SETMS(MS_IDLE);
      else
         return FALSE;
   }
   return TRUE;
}

The first time I sat down and write the basics of these functions, based on the mouse state diagram, things performed reasonably well. There were a number of minor issues which complicate things, but the main concept of simply using state and transition to determine new state has remained.

Thus, by thinking of the mouse logic in terms of a state machine, I was able to easily implement what I'd thought of as one of the more challenging parts of the program. The reason I thought this would be so difficult was because the way that I implemented mouse processing in the old version of Solitaire Pack was very complicated and ineffective.

For one thing, instead of having three event functions in the engine, I had one function which had to determine first what event had occurred. Just for the sake of comparison, the following function from the old version is that which is roughly equivalent to the functions above. Don't even try to understand the code; I didn't even understand it after not looking at it for nearly a year.

// Function:         CardEventHandler
//
// Purpose:          Handles MSHTML events from the pile's SPAN element and from child card
//                   elements.
//
// Parameters:
//    dispidEvent:   ID of the event that took place.
//    pPile:         Pile which contains the card.
//    pCard:         Card which is sending the event. If NULL, the event is from the pile.
//
// Returns:          None.
//
void CAppImpl::CardEventHandler(DISPID dispidEvent, CSolpackPile *pPile, CSolpackCard *pCard)
{
   if(dispidEvent == DISPID_HTMLELEMENTEVENTS2_ONMOUSEUP)
   {
      if(m_bAnimWon)
      {
         CancelWon();
         return;
      }
      if(m_bBusy)
      {
         m_bCancelAnims = TRUE;
         return;
      }
   }
   if(m_bBusy)
      return;
   if(!pPile)
   {
      //OutputDebugString(TEXT("CardEventHandler: No Pile\n"));
      return;
   }

   IHTMLEventObj *pEventObj = NULL;
   if(m_pHTMLWindow2)
      m_pHTMLWindow2->get_event(&pEventObj);

   if(!pEventObj)
      return;

   HRESULT hrPileClicked = E_FAIL;

   switch(dispidEvent)
   {
      case DISPID_HTMLELEMENTEVENTS2_ONMOUSEDOWN:
         if(!m_pDragSource && (m_lMouseState == CARD_MS_NONE))
         {
            long lButton = 0;
            pEventObj->get_button(&lButton);
            m_lMouseState = lButton;
            //if(lButton & CARD_MS_LEFT)
            //{
               if(pCard)   // Clicked on a card
               {
                  m_pCardSource = pCard;
                  m_pCardSource->AddRef();
                  pEventObj->get_offsetX(&m_lOffsetX);
                  pEventObj->get_offsetY(&m_lOffsetY);
//                m_lOffsetX += pCard->m_lX;
//                m_lOffsetY += pCard->m_lY;
               } else      // Clicked on an empty pile
               {
                  m_pDragSource2 = pPile;
                  m_pDragSource2->AddRef();
                  m_bCheckDistrib = TRUE;
                  //m_lMouseState |= CARD_NOCARD;
               }
            //}
         }
         break;
      case DISPID_HTMLELEMENTEVENTS2_ONMOUSEMOVE:
         //OutputDebugString(TEXT("CardEventHandler: ONMOUSEMOVE"));
         //if(!(m_lMouseState & CARD_MS_LEFT))
         // break;
         if(!m_pDragSource && (m_lMouseState == CARD_MS_LEFT))
         {
            //OutputDebugString(TEXT(" > CARD_MS_LEFT"));
            if(pCard && (pCard == m_pCardSource))
            {  // We just started a drag
               if(pCard->m_bCanDrag)
               {
                  //OutputDebugString(TEXT(" > Same Card"));
                  ISolpackCard *pSPCard = NULL;
                  if(SUCCEEDED(pCard->QueryInterface(IID_ISolpackCard, (LPVOID*)&pSPCard)))
                  {
                     ISolpackPile *pSPPile = NULL;
                     if(SUCCEEDED(pPile->QueryInterface(IID_ISolpackPile, (LPVOID*)&pSPPile)))
                     {
                        ISolpackPile *pSPFrag = NULL;
                        if(SUCCEEDED(m_pFragment->QueryInterface(IID_ISolpackPile,
                           (LPVOID*)&pSPFrag)))
                        {
                           long lType = 0;
                           pPile->get_Type(&lType);
                           m_pFragment->SetType(lType);
                           long lIndexOfCard = -1;
                           pPile->IndexOfCard(pSPCard, &lIndexOfCard);
                           if(lIndexOfCard != -1)
                           {
                              VARIANT_BOOL bOnlyCard = VARIANT_FALSE;
                              if(SUCCEEDED(m_pCurrentGame->SetupFragment(pSPPile, pSPCard,
                                 lIndexOfCard, pSPFrag, &bOnlyCard)))
                              {
                                 m_pFragment->m_pFragSrc = pPile;
                                 long lNewX = 0;
                                 long lNewY = 0;
                                 pEventObj->get_x(&lNewX);
                                 pEventObj->get_y(&lNewY);
                                 //CSolpackCard *pFirstCard = m_pFragment->GetFirstCard();
                                 //if(pFirstCard)
                                 {
                                    m_lOffsetX += pCard->m_lX;
                                    m_lOffsetY += pCard->m_lY;
                                    //pFirstCard = NULL;
                                 }
                                 m_pFragment->put_X(lNewX - m_lOffsetX);
                                 m_pFragment->put_Y(lNewY - m_lOffsetY);//(m_lY + lNewY);
                                 m_pFragment->UpdateSpanPos(0, 0, USP_X | USP_Y);
                                 if(bOnlyCard == VARIANT_TRUE)
                                 {
                                    IDispatch *pUnkCard = NULL;
                                    if(SUCCEEDED(pSPCard->QueryInterface(IID_IDispatch,
                                       (LPVOID*)&pUnkCard)))
                                    {
                                       m_pFragment->AddToFragment(pUnkCard);
                                       SAFERELEASE(pUnkCard);
                                    }
                                 } 
                                 long lNumCardsAfter = 0;
                                 m_pFragment->get_NumCards(&lNumCardsAfter);
                                 if(lNumCardsAfter > 0)
                                 {
                                    SAFERELEASE(m_pDragSource);
                                    m_pDragSource = pPile;
                                    m_pDragSource->AddRef();
                                    //m_lMouseState |= CARD_MS_DRAGGING;
                                    IHTMLElement2 *pEl2 = NULL;
                                    if(SUCCEEDED(m_pDragSource->m_pElPileSpan->QueryInterface
                                       (IID_IHTMLElement2, (LPVOID*)&pEl2)))
                                    {
                                       pEl2->setCapture(VARIANT_TRUE);
                                       SAFERELEASE(pEl2);
                                       m_lNeedToRelease = NTR_PILE;
                                       //m_bDragging = TRUE;
                                    }
                                 }
                              }
                           }
                        }
                     }
                     SAFERELEASE(pSPPile);
                  }
                  SAFERELEASE(pSPCard);
               } else
               {
                  // Can't drag it, but keep track anyways
                  //SAFERELEASE(m_pDragSource);
                  //m_pDragSource = pPile;
                  //m_pDragSource->AddRef();
                  //m_lMouseState |= CARD_MS_DRAGGING;
                  //OutputDebugString(TEXT("Left Drag\n"));
                  m_pDragSource2 = pPile;
                  m_pDragSource2->AddRef();
                  m_bCheckDistrib = TRUE;
               /* IHTMLElement2 *pEl2 = NULL;
                  if(SUCCEEDED(pPile->m_pElPileSpan->QueryInterface
                     (IID_IHTMLElement2, (LPVOID*)&pEl2)))
                  {
                     pEl2->setCapture(VARIANT_TRUE);
                     SAFERELEASE(pEl2);
                     m_lNeedToRelease = NTR_PILE;
                  }*/
               }
            } else
            {
               m_pDragSource2 = pPile;
               m_pDragSource2->AddRef();
               m_bCheckDistrib = TRUE;
               //OutputDebugString(TEXT("Cards don't match or no card\n"));
            }
         } else if(m_pDragSource)
         {
            //OutputDebugString(TEXT(" > has drag source"));
            long lNewX = 0;
            long lNewY = 0;
            pEventObj->get_x(&lNewX);
            pEventObj->get_y(&lNewY);
            m_pFragment->put_X(lNewX - m_lOffsetX);
            m_pFragment->put_Y(lNewY - m_lOffsetY);//(m_lY + lNewY);
            m_pFragment->UpdateSpanPos(0, 0, USP_X | USP_Y);
         } else //if(!m_bDragging)// Must be a different button down
         {
         // OutputDebugString(TEXT(" > some other button\n"));
            long lButton = 0;
            pEventObj->get_button(&lButton);
            if((lButton == CARD_MS_RIGHT) && (!m_lNeedToRelease))
            {
               m_bCheckDistrib = TRUE;
               //OutputDebugString(TEXT(" > button not none"));
               //IHTMLElement2 *pEl2 = NULL;
               /*HRESULT hr;
               if(pCard)
               {
                  hr = pCard->m_pElCard->QueryInterface(IID_IHTMLElement2, (LPVOID*)&pEl2);
                  if(SUCCEEDED(hr))
                     m_lNeedToRelease = NTR_CARD;
               } else
               {
                  hr = pPile->m_pElPileSpan->QueryInterface(IID_IHTMLElement2, (LPVOID*)&pEl2);
                  if(SUCCEEDED(hr))
                     m_lNeedToRelease = NTR_PILE;
               }*/
               /*if(SUCCEEDED(pPile->m_pElPileSpan->QueryInterface
                  (IID_IHTMLElement2, (LPVOID*)&pEl2)))
               {
                  pEl2->setCapture(VARIANT_TRUE);
                  SAFERELEASE(pEl2);
                  m_lNeedToRelease = NTR_PILE;
               }*/
            }
         }
         //OutputDebugString(TEXT("\n"));
         break;
      case DISPID_HTMLELEMENTEVENTS2_ONMOUSEUP:
         {
            if(pCard)
            {
               pEventObj->put_cancelBubble(VARIANT_TRUE);
            }
            CSolpackCard *pOldCardSource = m_pCardSource;
            m_pCardSource = NULL;
            long lButton = 0;
            pEventObj->get_button(&lButton);
            if(m_lNeedToRelease)
            {
               IHTMLElement2 *pEl2 = NULL;
               /*HRESULT hr;
               if(m_lNeedToRelease == NTR_CARD)
               {
                  if(pCard)
                     hr = pCard->m_pElCard->QueryInterface(IID_IHTMLElement2, (LPVOID*)&pEl2);
               } else
               {
                  hr = pPile->m_pElPileSpan->QueryInterface(IID_IHTMLElement2, (LPVOID*)&pEl2);
               }
               if(SUCCEEDED(hr))*/
               if(SUCCEEDED(pPile->m_pElPileSpan->QueryInterface
                  (IID_IHTMLElement2, (LPVOID*)&pEl2)))
               {
                  pEl2->releaseCapture();
                  SAFERELEASE(pEl2);
               }
               m_lNeedToRelease = NTR_NONE;
            }
            if(m_pDragSource /*&& m_bDragging*/)
            {
               if((lButton & CARD_MS_LEFT) && m_pDragSource)   // No left anymore
               {
                  m_lMouseState = CARD_MS_NONE;
                  //m_lMouseState &= ~CARD_MS_DRAGGING;
                  long lX = 0;
                  long lY = 0;
                  pEventObj->get_x(&lX);
                  pEventObj->get_y(&lY);
                  HandleDragEnd(m_pFragment, lX, lY);
                  SAFERELEASE(m_pDragSource);
               }
            } else // if((pCard && (pCard == pOldCardSource)) || (m_lMouseState & CARD_NOCARD))
            //{
               if((m_lMouseState & CARD_MS_LEFT) && (lButton & CARD_MS_LEFT))
                  // Left was clicked once
               {
                  //m_lMouseState = CARD_MS_NONE;
                  ISolpackPile *pSPThisPile = NULL;
                  if(SUCCEEDED(pPile->QueryInterface(IID_ISolpackPile, (LPVOID*)&pSPThisPile)))
                  {
                     if(pCard)
                     {
                        if(m_pCurrentGame/*&& (g_cAnims == 0)*/)
                        {
                           BOOL bValid = FALSE;
                           if(pCard == pOldCardSource)
                              bValid = TRUE;
                           else if(m_bCheckDistrib)
                           {
                              ISolpackPile *pCardsPile = NULL;
                              if(pOldCardSource && SUCCEEDED(pOldCardSource->get_Pile(&pCardsPile)))
                              {
                                 if(pCardsPile == pSPThisPile)
                                    bValid = TRUE;
                                 SAFERELEASE(pCardsPile);
                              }
                           }
                           if(bValid)
                              hrPileClicked = m_pCurrentGame->PileClicked(pSPThisPile);
                        }
                     } else
                     {
                        if(m_bCheckDistrib && m_pCurrentGame)
                        {
                           if(m_pDragSource2 && (m_pDragSource2 == pPile))
                              hrPileClicked = m_pCurrentGame->PileClicked(pSPThisPile);
                        }
                     // OutputDebugString(TEXT("Dragged away\n"));
                     }
                     SAFERELEASE(pSPThisPile);
                  }
                  //bSetToNone = TRUE;
               }
             else if((m_lMouseState & CARD_MS_RIGHT) &&
                  (lButton & CARD_MS_RIGHT))
            {
               // Right was clicked once
               if(pCard && (pCard == pOldCardSource))
               {
                  m_lMouseState = CARD_MS_NONE;
                  if(pPile->AutoPlayCard(pCard) == S_OK)
                     PushNewTurn();
                  //bSetToNone = TRUE;
               }
               //m_lMouseState = CARD_MS_NONE;
            }
             m_lMouseState = CARD_MS_NONE;
            m_bCheckDistrib = FALSE;
            SAFERELEASE(m_pDragSource2);
            SAFERELEASE(pOldCardSource);
         }
         break;
   }

   if(hrPileClicked == S_OK)
   {
      PushNewTurn();
   }

   SAFERELEASE(pEventObj);
}

There are so many if statements, so many different things kept track of in different places, so many lines commented out. The code was confusing and messy. Using a state machine in the new version is not solely responsible for the cleaner code this time around; my own programming practices have improved dramatically since that time. Also, other parts of my architecture have improved greatly, allowing for simpler and cleaner code.

Overall, using a state machine to process mouse events has made the development of what was potentially the most complicated logic in the program relatively simple. In future development projects, I will definitely be on the lookout for places in which using a state machine can simplify program design and implementation.

  April 24, 2002 jmhoersc@mtu.edu