/***************************************************************************/ /** Microsoft Windows **/ /** Copyright(c) Microsoft Corp., 1991, 1992 **/ /***************************************************************************/ /**************************************************************************** main2.cpp Aug 92, JimH May 93, JimH chico port Additional member functions for CMainWindow are here. ****************************************************************************/ #include "hearts.h" #include "main.h" #include "resource.h" #include "debug.h" /**************************************************************************** CMainWindow::Shuffle -- user requests shuffle from menu ****************************************************************************/ void CMainWindow::Shuffle() { static int offset[MAXPLAYER] = { 1, 3, 2, 0 }; // passdir order // fill temp array with consecutive values int temp[52]; // array of card values for (int i = 0; i < 52; i++) temp[i] = i; // Sort cards int nLeft = 52; for (i = 0; i < 52; i++) { int j = ::rand() % nLeft; int id = i/13; int pos = Id2Pos(id); // convert id to position p[pos]->SetID(i%13, temp[j]); p[pos]->Select(i%13, FALSE); temp[j] = temp[--nLeft]; } // display PASS button if (passdir != NOPASS) { CString text; text.LoadString(IDS_PASSLEFT + passdir); m_Button.SetWindowText(text); m_Button.EnableWindow(FALSE); m_Button.ShowWindow(SW_SHOW); } // set card locs and ask players to select cards to pass for (i = 0; i < MAXPLAYER; i++) { p[i]->ResetLoc(); if (passdir != NOPASS) p[i]->SelectCardsToPass(); } // Make sure everyone gets appropriate little white dots if (passdir != NOPASS) { if (role == PLAYER) ddeClient->Poke(hszPassUpdate, TEXT("")); // ask for update else ddeServer->PostAdvise(hszPass); // inform players } // Paint main window. This is done manually instead of just // invalidating the rectangle so that the cards are drawn in // order as if they are dealt, instead of a player at a time. CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif CRect rect; GetClientRect(rect); dc.FillRect(&rect, &m_BgndBrush); for (SLOT s = 0; s < MAXSLOT; s++) for (i = 0; i < MAXPLAYER; i++) p[i]->Draw(dc, bCheating, s); for (i = 0; i < MAXPLAYER; i++) { if (passdir == NOPASS) p[i]->NotifyNewRound(); else { p[i]->MarkSelectedCards(dc); CString sSelect; sSelect.LoadString(IDS_SELECT); CString sName; int passto = (i + offset[passdir]) % 4; sName = p[passto]->GetName(); TCHAR string[100]; wsprintf(string, sSelect, sName); p[i]->UpdateStatus(string); } } DoSort(); } /**************************************************************************** CMainWindow::HandlePassing() This function first checks to make sure each player is DONE_SELECTING, and then transfers the cards from hand to hand. This function is called by the gamemeister when he presses the pass button, or when notification arrives that a remote human has selected cards to pass. It returns FALSE if cards were not passed (because a remote human was still selecting) and TRUE if cards were successfully passed. ****************************************************************************/ BOOL CMainWindow::HandlePassing() { int passto[MAXPLAYER]; int temp[MAXPLAYER][3]; static int offset[MAXPLAYER] = { 1, 3, 2, 0 }; for (int pos = 0; pos < MAXPLAYER; pos++) if (p[pos]->GetMode() != DONE_SELECTING) return FALSE; for (pos = 0; pos < MAXPLAYER; pos++) { passto[pos] = ((pos + offset[passdir]) % 4); p[pos]->ReturnSelectedCards(temp[pos]); } for (pos = 0; pos < MAXPLAYER; pos++) p[passto[pos]]->ReceiveSelectedCards(temp[pos]); for (pos = 0; pos < MAXPLAYER; pos++) if (bCheating || (pos == 0)) p[pos]->Sort(); tricksleft = MAXSLOT; passdir++; if (passdir > NOPASS) passdir = LEFT; for (pos = 0; pos < MAXPLAYER; pos++) p[pos]->NotifyNewRound(); // notify players cards are passed CString s; s.LoadString(IDS_OK); m_Button.SetWindowText(s); OnShowButton(); for (pos = 0; pos < MAXPLAYER; pos++) { CRect rect; if (pos == 0 || bCheating) p[pos]->GetCoverRect(rect); else p[pos]->GetMarkingRect(rect); InvalidateRect(&rect, TRUE); } UpdateWindow(); return TRUE; } /**************************************************************************** CMainWindow::FirstMove resets cardswon[] and tells owner of two of clubs to start hand ****************************************************************************/ void CMainWindow::FirstMove() { for (int pos = 0; pos < MAXPLAYER; pos++) { p[pos]->SetMode(WAITING); p[pos]->ResetCardsWon(); } for (pos = 0; pos < MAXPLAYER; pos++) { for (SLOT s = 0; s < MAXSLOT; s++) { if (p[pos]->GetID(s) == TWOCLUBS) { int id = Pos2Id(pos); ResetHandInfo(id); handinfo.bHeartsBroken = FALSE; handinfo.bQSPlayed = FALSE; handinfo.bShootingRisk = TRUE; handinfo.nMoonShooter = EMPTY; handinfo.bHumanShooter = FALSE; p[pos]->SelectCardToPlay(handinfo, bCheating); if (pos != 0) ((local_human *)p[0])->WaitMessage(p[pos]->GetName()); return; } } } } /**************************************************************************** CMainWindow::EndHand TimerDispatch CMainWindow::DispatchCards The Ref calls this routine at the end of each hand. It is logically a single routine, but is broken up so that there is a delay before the cards are zipped off the screen. EndHand() calculates who won the hand (trick) and starts a timer. TimerDispatch() receives the time message and calls DispatchCards(). DispatchCards() ****************************************************************************/ void CMainWindow::EndHand() { /* determine suit led */ int playerled = handinfo.playerled; card *cardled = handinfo.cardplayed[playerled]; int suitled = cardled->Suit(); int value = cardled->Value2(); trickwinner = playerled; // by default // Let players update tables, etc. for (int i = 0; i < 4; i++) p[i]->NotifyEndHand(handinfo); // check if anyone else played a higher card of the same suit for (i = playerled; i < (playerled+4); i++) { int j = i % 4; card *c = handinfo.cardplayed[j]; if (c->Suit() == suitled) { int v = c->Value2(); if (v > value) { value = v; trickwinner = j; } } } TRACE0("\n"); // Update moonshoot portion of handinfo if (handinfo.bShootingRisk) { BOOL bPoints = FALSE; // point cards this hand? for (i = 0; i < 4; i++) { card *c = handinfo.cardplayed[i]; if ((c->Suit() == HEARTS) || (c->ID() == BLACKLADY)) bPoints = TRUE; } if (bPoints) { if (handinfo.nMoonShooter == EMPTY) { handinfo.nMoonShooter = trickwinner; // first points this round handinfo.bHumanShooter = p[trickwinner]->IsHuman(); TRACE2("First points to p[%d] (%s)\n", trickwinner, handinfo.bHumanShooter ? TEXT("human") : TEXT("computer")); } else if (handinfo.nMoonShooter != trickwinner) // new point earner { handinfo.bShootingRisk = FALSE; TRACE0("Moon shot risk over\n"); } } } // Start a timer so there is a delay between when the last card of // the trick is played, and when the cards are whisked off toward // the trick winner (dispatched.) If the timer fails, just call // DispatchCards() directly. The timer id is m_myid instead of a // constant so there's no conflict if you run multiple instances on // a single machine using local DDE, which is useful for testing. if (SetTimer(m_myid, 1000, TimerDispatch)) bTimerOn = TRUE; else { bTimerOn = FALSE; DispatchCards(); } } // for MFC1, this would return UINT and 3rd parameter would be int // for MFC2, this would return VOID and 3rd parameter would be UINT #if defined (MFC1) inline UINT FAR PASCAL EXPORT TimerDispatch(HWND hWnd, UINT nMsg, int nIDEvent, DWORD dwTime) { ::pMainWnd->DispatchCards(); // sneak back into a CMainWindow member func. return 0; } #else inline VOID FAR PASCAL EXPORT TimerDispatch(HWND hWnd, UINT nMsg, UINT_PTR nIDEvent, DWORD dwTime) { ::pMainWnd->DispatchCards(); // sneak back into a CMainWindow member func. } #endif void CMainWindow::DispatchCards() { KillTimer(m_myid); bTimerOn = FALSE; int score[MAXPLAYER]; int poswinner = Id2Pos(trickwinner); // Determine who led so cards can be removed in reverse order. int playerled = handinfo.playerled; card *cardled = handinfo.cardplayed[playerled]; // build up background bitmap for Glide() for (int i = (playerled + 3); i >= playerled; i--) { CDC *memdc = new CDC; CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif memdc->CreateCompatibleDC(&dc); memdc->SelectObject(&card::m_bmBgnd); memdc->SelectObject(&m_BgndBrush); memdc->PatBlt(0, 0, card::dxCrd, card::dyCrd, PATCOPY); card *c = handinfo.cardplayed[i % 4]; // If cards overlap, there is some extra work to do because the cards // still in player 0's or 2's hands may overlap cards that have been // played, so they have to get blted in first. if (TRUE) // bugbug should be able to check for overlap here { for (int pos = 0; pos < MAXPLAYER; pos += 2) { int mode = ((pos == 0 || bCheating) ? FACEUP : FACEDOWN); for (SLOT s = 0; s < MAXSLOT; s++) { card *c2 = p[pos]->Card(s); int x = c2->GetX() - c->GetX(); int y = c2->GetY() - c->GetY(); if (!c2->IsPlayed()) c2->Draw(*memdc, x, y, mode, FALSE); } } } // Everyone needs to check for overlap of played cards. for (int j = playerled; j < i; j++) { card *c2 = handinfo.cardplayed[j % 4]; int x = c2->GetX() - c->GetX(); int y = c2->GetY() - c->GetY(); c2->Draw(*memdc, x, y, FACEUP, FALSE); } delete memdc; p[poswinner]->WinCard(dc, c); c->Remove(); } ResetHandInfo(trickwinner); // If there are more tricks left before we need to reshuffle, // ask the winner of this trick to start next hand, and we're done. if (--tricksleft) { p[poswinner]->SelectCardToPlay(handinfo, bCheating); if (poswinner != 0) ((local_human *)p[0])->WaitMessage(p[poswinner]->GetName()); if (::cQdMoves > 0) { for (int i = 0; i < ::cQdMoves; i++) HandleMove(::moveq[i]); ::cQdMoves = 0; } return; } // Make sure sound buffer is freed up. HeartsPlaySound(OFF); // Display hearts (and queen of spades) next to whoever "won" them. int nMoonShot = EMPTY; // assume nobody shot moon for (i = 0; i < MAXPLAYER; i++) { BOOL bMoonShot; score[i] = p[i]->EvaluateScore(bMoonShot); if (bMoonShot) nMoonShot = i; // scores need to be adjusted CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif p[i]->DisplayHeartsWon(dc); p[i]->SetMode(SCORING); } // adjust scores if someone collected all hearts AND queen of spades if (nMoonShot != EMPTY) { for (i = 0; i < MAXPLAYER; i++) { if (i == nMoonShot) score[i] -= 26; else score[i] += 26; p[i]->SetScore(score[i]); // adjust player score manually } } // Show score p[0]->UpdateStatus(IDS_SCORE); p[0]->SetMode(SCORING); CScoreDlg scoredlg(this, score, m_myid); // update scores in scoredlg player *pold = p[0]; scoredlg.DoModal(); // display scores // If there has been a request to shut down while the score dialog // is displayed, m_FatalErrno will be non-zero. if (m_FatalErrno != 0) { p[0]->SetMode(PLAYING); // something other than SCORING... FatalError(m_FatalErrno); // so FatalError will accept it. return; } // It's possible for another player to have quit the game while // the score dialog was showing, so check that we're still // alive and well. if (p[0] != pold) return; // replace quit remote humans with computer players for (i = 1; i < MAXPLAYER; i++) { if (p[i]->HasQuit()) { CString name = p[i]->GetName(); int scoreLocal = p[i]->GetScore(); delete p[i]; p[i] = new computer(i); // check for failure CClientDC dc(this); p[i]->SetName(name, dc); p[i]->SetScore(scoreLocal); } } p[0]->SetMode(passdir == NOPASS ? DONE_SELECTING : SELECTING); if (scoredlg.IsGameOver()) { GameOver(); return; } Shuffle(); // If there is no passing for upcoming round, we must make the changes // that HandlePassing() would normally do to start the next round. if (passdir == NOPASS) { for (i = 0; i < MAXPLAYER; i++) // everyone's DONE_SELECTING p[i]->SetMode(DONE_SELECTING); passdir = LEFT; // NEXT hand passes left tricksleft = MAXSLOT; // reset # of hands FirstMove(); // start next trick } for (i = 0; i < ::cQdMoves; i++) HandleMove(::moveq[i]); ::cQdMoves = 0; for (i = 0; i < ::cQdPasses; i++) HandlePass(::passq[i]); ::cQdPasses = 0; } /**************************************************************************** CMainWindow::ResetHandInfo Note that handinfo.bHeartsBroken is not reset here -- it applies to the entire hand and is set only in FirstMove() Same with handinfo.bQSPlayed and moonshoot variables. ****************************************************************************/ void CMainWindow::ResetHandInfo(int playernumber) { handinfo.playerled = playernumber; handinfo.turn = playernumber; for (int i = 0; i < MAXPLAYER; i++) handinfo.cardplayed[i] = NULL; } /**************************************************************************** CMainWindow::CountClients() Count of number of clients active (including computer players) Only the GameMeister calls this, so potential clients are pos 1 to 3. ****************************************************************************/ int CMainWindow::CountClients() { ASSERT(role == GAMEMEISTER); int cb = 0; for (int pos = 1; pos < MAXPLAYER; pos++) if (p[pos]) cb++; return cb; }