608 lines
16 KiB
C++
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;
|
|
}
|