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

692 lines
17 KiB
C++

/***************************************************************************/
/** Microsoft Windows **/
/** Copyright(c) Microsoft Corp., 1991, 1992 **/
/***************************************************************************/
/****************************************************************************
human.cpp
Aug 92, JimH
May 93, JimH chico port
local_human and remote_human member functions
****************************************************************************/
#include "hearts.h"
#include "main.h" // friendly access
#include "resource.h"
#include "debug.h"
#include <stdio.h>
#include <stdlib.h> // abs() prototype
static CRect rectCard; // used in timer callback
// declare static members
BOOL local_human::bTimerOn;
CString local_human::m_StatusText;
/****************************************************************************
human constructor -- abstract class
****************************************************************************/
human::human(int n, int pos) : player(n, pos)
{
}
/****************************************************************************
remote_human constructor
****************************************************************************/
remote_human::remote_human(int n, int pos, HCONV hConv) : human(n, pos),
m_hConv(hConv), bQuit(FALSE)
{
}
/****************************************************************************
remote_human::SelectCardToPlay()
Under normal circumstances, all that is required is that mode be set
to PLAYING. If the remote human has quit and the computer player has
not yet taken over, this routine just picks the first legal card it
can find and plays it.
****************************************************************************/
void remote_human::SelectCardToPlay(handinfotype &h, BOOL bCheating)
{
if (!bQuit)
{
SetMode(PLAYING);
return;
}
BOOL bFirst = (h.playerled == id); // am I leading?
card *cardled = h.cardplayed[h.playerled];
int nSuitLed = (cardled == NULL ? EMPTY : cardled->Suit());
SLOT sLast[MAXSUIT]; // will contain some card of each suit
SLOT s = EMPTY;
for (int i = 0; i < MAXSUIT; i++)
sLast[i] = EMPTY;
// fill sLast array, and look for two of clubs while were are at it
for (i = 0; i < MAXSLOT; i++)
{
if (cd[i].IsValid())
{
sLast[cd[i].Suit()] = i;
if (cd[i].ID() == TWOCLUBS)
s = i;
}
}
if (s == EMPTY) // if two of clubs not found
{
if (sLast[CLUBS] != EMPTY)
s = sLast[CLUBS];
else if (sLast[DIAMONDS] != EMPTY)
s = sLast[DIAMONDS];
else if (sLast[SPADES] != EMPTY)
s = sLast[SPADES];
else
s = sLast[HEARTS];
if (!bFirst && (sLast[nSuitLed] != EMPTY))
s = sLast[nSuitLed];
}
SetMode(WAITING);
cd[s].Play(); // mark card as played
h.cardplayed[id] = &(cd[s]); // update handinfo
// inform other players
::move.playerid = id;
::move.cardid = cd[s].ID();
::move.playerled = h.playerled;
::move.turn = h.turn;
ddeServer->PostAdvise(hszMove);
// inform gamemeister
::pMainWnd->PostMessage(WM_COMMAND, IDM_REF);
}
/****************************************************************************
remote_human::SelectCardsToPass()
Under normal circumstances, all that is required is that mode be set to
SELECTING. If the remote human has quit and the computer player has not
yet taken over, just select the first three cards found.
****************************************************************************/
void remote_human::SelectCardsToPass()
{
if (!bQuit)
{
SetMode(SELECTING);
return;
}
cd[0].Select(TRUE);
cd[1].Select(TRUE);
cd[2].Select(TRUE);
for (int i = 3; i < MAXSLOT; i++)
cd[i].Select(FALSE);
CClientDC dc(::pMainWnd);
#ifdef USE_MIRRORING
SetLayout(dc.m_hDC, 0);
SetLayout(dc.m_hAttribDC, 0);
#endif
MarkSelectedCards(dc);
SetMode(DONE_SELECTING);
ddeServer->PostAdvise(hszPass); // let other players know
}
/****************************************************************************
local_human::local_human()
This is the constructor that initializes player::hWnd and player::hInst.
It also creates the stretch bitmap that covers a card plus its popped
height extension.
****************************************************************************/
local_human::local_human(int n) : human(n, 0)
{
m_pStatusWnd = new CStatusBarCtrl();
m_StatusText.LoadString(IDS_INTRO);
CClientDC dc(::pMainWnd);
m_pStatusWnd->Create(WS_CHILD|WS_VISIBLE|CCS_BOTTOM, CRect(), ::pMainWnd, 0);
m_pStatusWnd->SetSimple();
UpdateStatus();
bTimerOn = FALSE;
if (!m_bmStretchCard.CreateCompatibleBitmap(&dc, card::dxCrd,
card::dyCrd + POPSPACING))
{
::pMainWnd->FatalError(IDS_MEMORY);
return;
}
}
/****************************************************************************
local_human destructor
****************************************************************************/
local_human::~local_human()
{
m_bmStretchCard.DeleteObject();
delete m_pStatusWnd;
m_pStatusWnd = NULL;
}
/****************************************************************************
local_human::Draw()
This virtual function draws selected cards in the popped up position.
ALL is not used for slot in this variant.
****************************************************************************/
void local_human::Draw(CDC &dc, BOOL bCheating, SLOT slot)
{
DisplayName(dc);
SLOT start = (slot == ALL ? 0 : slot);
SLOT stop = (slot == ALL ? MAXSLOT : slot+1);
SLOT playedslot = EMPTY; // must draw cards in play last for EGA
for (SLOT s = start; s < stop; s++)
{
if (cd[s].IsPlayed())
playedslot = s;
else
cd[s].PopDraw(dc); // pop up selected cards
}
if (playedslot != EMPTY)
cd[playedslot].Draw(dc);
}
/****************************************************************************
local_human::PopCard()
handles mouse button selection of card to pass
****************************************************************************/
void local_human::PopCard(CBrush &brush, int x, int y)
{
SLOT s = XYToCard(x, y);
if (s == EMPTY)
return;
// count selected cards
int c = 0;
for (int i = 0; i < MAXSLOT; i++)
if (cd[i].IsSelected())
c++;
if (cd[s].IsSelected() && (c == 3))
{
::pMainWnd->PostMessage(WM_COMMAND, IDM_HIDEBUTTON);
}
else if (!cd[s].IsSelected())
{
if (c == 3) // only allow three selections
return;
else if (c == 2)
::pMainWnd->PostMessage(WM_COMMAND, IDM_SHOWBUTTON);
}
// toggle selection
BOOL bSelected = cd[s].IsSelected();
cd[s].Select(!bSelected);
CClientDC dc(::pMainWnd);
#ifdef USE_MIRRORING
SetLayout(dc.m_hDC, 0);
SetLayout(dc.m_hAttribDC, 0);
#endif
CDC memDC;
memDC.CreateCompatibleDC(&dc);
memDC.SelectObject(&m_bmStretchCard);
memDC.SelectObject(&brush);
memDC.PatBlt(0, 0, card::dxCrd, card::dyCrd + POPSPACING, PATCOPY);
for (i = 0; i < MAXSLOT; i++)
{
if (abs(i - s) <= (card::dxCrd / HORZSPACING))
{
cd[i].Draw(memDC, // cdc
(i - s) * HORZSPACING, // x
cd[i].IsSelected() ? 0 : POPSPACING, // y
FACEUP, // mode
FALSE); // update loc?
}
}
dc.BitBlt(loc.x + (HORZSPACING * s), loc.y - POPSPACING,
card::dxCrd, card::dyCrd + POPSPACING,
&memDC, 0, 0, SRCCOPY);
}
/****************************************************************************
local_human::PlayCard()
handles mouse button selection of card to play
and ensures move is legal.
PlayCard starts a timer that calls StartTimer() which calls TimerBadMove().
Think of it as one long function with a timer delay half way through.
****************************************************************************/
BOOL local_human::PlayCard(int x, int y, handinfotype &h, BOOL bCheating,
BOOL bFlash)
{
SLOT s = XYToCard(x, y);
if (s == EMPTY)
return FALSE;
card *cardled = h.cardplayed[h.playerled];
BOOL bFirstTrick = (cardled != NULL && cardled->ID() == TWOCLUBS);
/* check if selected card is valid */
if (h.playerled == id) // if local human is leading...
{
if (cd[s].ID() != TWOCLUBS)
{
for (int i = 0; i < MAXSLOT; i++) // is there a two of clubs?
{
if ((i != s) && (cd[i].ID() == TWOCLUBS))
{
UpdateStatus(IDS_LEAD2C);
if (bFlash)
StartTimer(cd[s]);
return FALSE;
}
}
}
if ((cd[s].Suit() == HEARTS) && (!h.bHeartsBroken)) // if hearts led
{
for (int i = 0; i < MAXSLOT; i++) // are there any non-hearts?
{
if ((!cd[i].IsEmpty()) && (cd[i].Suit() != HEARTS))
{
UpdateStatus(IDS_LEADHEARTS);
if (bFlash)
StartTimer(cd[s]);
return FALSE;
}
}
}
}
// if not following suit
else if (cardled != NULL && (cd[s].Suit() != cardled->Suit()))
{
// make sure we're following suit if possible
for (int i = 0; i < MAXSLOT; i++)
{
if ((!cd[i].IsEmpty()) && (cd[i].Suit()==cardled->Suit()))
{
CString s1, s2;
s1.LoadString(IDS_BADMOVE);
s2.LoadString(IDS_SUIT0+cardled->Suit());
TCHAR string[80];
wsprintf(string, s1, s2);
if (bFlash)
{
UpdateStatus(string);
StartTimer(cd[s]);
}
return FALSE;
}
}
// make sure we're not trying to break the First Blood rule
if (bFirstTrick && ::pMainWnd->IsFirstBloodEnforced())
{
BOOL bPointCard =
(cd[s].Suit() == HEARTS || cd[s].ID() == BLACKLADY);
BOOL bOthersAvailable = FALSE;
for (int i = 0; i < MAXSLOT; i++)
if ((!cd[i].IsEmpty()) && (cd[i].Suit() != HEARTS))
if (cd[i].ID() != BLACKLADY)
bOthersAvailable = TRUE;
if (bPointCard && bOthersAvailable)
{
UpdateStatus(IDS_BADBLOOD);
if (bFlash)
StartTimer(cd[s]);
return FALSE;
}
}
}
SetMode(WAITING);
cd[s].Play();
h.cardplayed[id] = &(cd[s]);
::move.playerid = id;
::move.cardid = cd[s].ID();
::move.playerled = h.playerled;
::move.turn = h.turn;
if (id == 0) // if gamemeister
ddeServer->PostAdvise(hszMove);
else
ddeClient->Poke(hszMove, &move, sizeof(move));
::pMainWnd->OnRef();
return TRUE;
}
void local_human::StartTimer(card &c)
{
CClientDC dc(::pMainWnd);
#ifdef USE_MIRRORING
SetLayout(dc.m_hDC, 0);
SetLayout(dc.m_hAttribDC, 0);
#endif
c.Draw(dc, HILITE); // flash
c.GetRect(rectCard);
if (::pMainWnd->SetTimer(1, 250, TimerBadMove))
{
bTimerOn = TRUE;
}
else
{
bTimerOn = FALSE;
::pMainWnd->InvalidateRect(&rectCard, FALSE);
}
}
// MFC2 changes same as SetTimer in main2.cpp
#if defined (MFC1)
UINT FAR PASCAL EXPORT
TimerBadMove(HWND hWnd, UINT nMsg, int nIDEvent, DWORD dwTime)
{
::KillTimer(hWnd, 1);
local_human::bTimerOn = FALSE;
::InvalidateRect(hWnd, &rectCard, FALSE);
return 0;
}
#else
void FAR PASCAL EXPORT
TimerBadMove(HWND hWnd, UINT nMsg, UINT_PTR nIDEvent, DWORD dwTime)
{
::KillTimer(hWnd, 1);
local_human::bTimerOn = FALSE;
#ifdef USE_MIRRORING
CRect rect;
int i;
DWORD ProcessDefaultLayout;
if (GetProcessDefaultLayout(&ProcessDefaultLayout))
if (ProcessDefaultLayout == LAYOUT_RTL)
{
GetClientRect(hWnd, &rect);
rectCard.left = abs(rect.right - rect.left) - rectCard.left;
rectCard.right = abs(rect.right - rect.left) - rectCard.right;
i = rectCard.left;
rectCard.left = rectCard.right;
rectCard.right = i;
}
#endif
::InvalidateRect(hWnd, &rectCard, FALSE);
}
#endif
/****************************************************************************
local_human::XYToCard()
returns a card slot number (or EMPTY) given a mouse location
****************************************************************************/
int local_human::XYToCard(int x, int y)
{
// check that we are in the right general area on the screen
if (y < (loc.y - POPSPACING))
return EMPTY;
if (y > (loc.y + card::dyCrd))
return EMPTY;
if (x < loc.x)
return EMPTY;
if (x > (loc.x + (12 * HORZSPACING) + card::dxCrd))
return EMPTY;
// Take first stab at card selected.
SLOT s = (x - loc.x) / HORZSPACING;
if (s > 12)
s = 12;
// If the click is ABOVE the top of the normal card location,
// check to see if this is a selected card.
if (y < loc.y)
{
// If the card is bSelected, then we have it. If not, it could
// be overhanging other cards.
if (!cd[s].IsSelected())
{
for (;;)
{
if (s == 0)
return EMPTY;
s--;
// if this card doesn't extend as far as x, give up
if ((loc.x + (s * HORZSPACING) + card::dxCrd) < x)
return EMPTY;
// if this card is selected, we've got it
if (cd[s].IsSelected())
break;
}
}
}
// a similar check is used to make sure we pick a card not yet played
if (!cd[s].IsInHand())
{
for (;;)
{
if (s == 0)
return EMPTY;
s--;
// if this card doesn't extend as far as x, give up
if ((loc.x + (s * HORZSPACING) + card::dxCrd) < x)
return EMPTY;
// if this card is selected, we've got it
if (cd[s].IsInHand())
break;
}
}
return s;
}
/****************************************************************************
local_human::SelectCardsToPass()
This virtual function allows mouse clicks to mean select a card to play.
****************************************************************************/
void local_human::SelectCardsToPass()
{
SetMode(SELECTING);
}
/****************************************************************************
local_human::SelectCardToPlay
Computer versions of this virtual function actually do the card selection.
This local_human version marks the player as ready to select a card to
play with the mouse, and updates the status to reflect this.
****************************************************************************/
void local_human::SelectCardToPlay(handinfotype &h, BOOL bCheating)
{
SetMode(PLAYING);
UpdateStatus(IDS_GO);
}
/****************************************************************************
local_human::UpdateStatus
The status bar can be updated either by manually filling m_StatusText
or by passing a string resource id.
****************************************************************************/
void local_human::UpdateStatus(void)
{
m_pStatusWnd->SetText(m_StatusText, 255, 0);
}
void local_human::UpdateStatus(int stringid)
{
status = stringid;
m_StatusText.LoadString(stringid);
UpdateStatus();
}
void local_human::UpdateStatus(const TCHAR *string)
{
m_StatusText = string;
UpdateStatus();
}
/****************************************************************************
local_human::ReceiveSelectedCards
The parameter c[] is an array of three cards being passed from another
player.
****************************************************************************/
void local_human::ReceiveSelectedCards(int c[])
{
for (int i = 0, j = 0; j < 3; i++)
{
if (cd[i].IsSelected())
cd[i].SetID(c[j++]);
ASSERT(i < MAXSLOT);
}
SetMode(ACCEPTING);
UpdateStatus(IDS_ACCEPT);
}
/****************************************************************************
local_human::WaitMessage()
Makes and shows the "Waiting for %s to move..." message
****************************************************************************/
void local_human::WaitMessage(const TCHAR *name)
{
TCHAR buf[100];
CString s;
s.LoadString(IDS_WAIT);
wsprintf(buf, s, name);
UpdateStatus(buf);
}