windows-nt/Source/XPSP1/NT/shell/osshell/games/mshearts/main2.cpp
2020-09-26 16:20:57 +08:00

608 lines
16 KiB
C++

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