/***************************************************************************/ /** Microsoft Windows **/ /** Copyright(c) Microsoft Corp., 1991, 1992 **/ /***************************************************************************/ /**************************************************************************** main.cpp Aug 92, JimH May 93, JimH chico port Main window callback functions Other CMainWindow member functions are in main2.cpp, welcome.cpp and ddecb.cpp ****************************************************************************/ #include "hearts.h" #include "main.h" #include "resource.h" #include "debug.h" #include #if defined (WINDOWS_ME) && ! defined (USE_MIRRORING) DWORD meMsgBox=0; DWORD meSystem=0; #define NLS_RESOURCE_LOCALE_KEY "Control Panel\\desktop\\ResourceLocale" #endif // declare static memberes CBrush CMainWindow::m_BgndBrush; CRect CMainWindow::m_TableRect; // declare globals CMainWindow *pMainWnd; DDE *dde; // same as either ddeClient or ddeServer HSZ hszJoin; // string handles used by DDE HSZ hszPass; HSZ hszMove; HSZ hszStatus; HSZ hszGameNumber; HSZ hszPassUpdate; MOVE move; // describes move for DDE transaction MOVE moveq[8]; // queue of moves waiting to be handled int cQdMoves; // number of moves in above queue PASS3 passq[4]; // queue of passes waiting to be handled int cQdPasses; // number of passes in above queue int nStatusHeight; // height of status window // Do not translate these registry strings const TCHAR szRegPath[] = REGSTR_PATH_WINDOWSAPPLETS TEXT("\\Hearts"); const TCHAR regvalSound[] = TEXT("sound"); const TCHAR regvalName[] = TEXT("name"); const TCHAR regvalRole[] = TEXT("gamemeister"); const TCHAR regvalServer[] = TEXT("server"); const TCHAR regvalSpeed[] = TEXT("speed"); const TCHAR *regvalPName[3] = { TEXT("p1name"), TEXT("p2name"), TEXT("p3name") }; const TCHAR szHelpFileName[] = TEXT("mshearts.chm"); const TCHAR szShareName[] = TEXT("HEARTS$"); CTheApp theApp; // start Hearts and run it! /**************************************************************************** CTheApp::InitInstance ****************************************************************************/ BOOL CTheApp::InitInstance() { m_pMainWnd = new CMainWindow(m_lpCmdLine); m_pMainWnd->ShowWindow(SW_SHOW); // instead of m_nCmdShow m_pMainWnd->UpdateWindow(); // Start the app off by posting Welcome dialog. m_pMainWnd->PostMessage(WM_COMMAND, IDM_WELCOME); return TRUE; } BEGIN_MESSAGE_MAP( CMainWindow, CFrameWnd ) ON_COMMAND(IDM_ABOUT, OnAbout) ON_COMMAND(IDM_BOSSKEY, OnBossKey) ON_COMMAND(IDM_CHEAT, OnCheat) ON_COMMAND(IDM_EXIT, OnExit) ON_COMMAND(IDM_HELP, OnHelp) // ON_COMMAND(IDM_HELPONHELP, OnHelpOnHelp) ON_COMMAND(IDM_HIDEBUTTON, OnHideButton) // ON_COMMAND(IDM_SEARCH, OnSearch) ON_COMMAND(IDM_NEWGAME, OnNewGame) ON_COMMAND(IDM_OPTIONS, OnOptions) ON_COMMAND(IDM_QUOTE, OnQuote) ON_COMMAND(IDM_REF, OnRef) ON_COMMAND(IDM_SHOWBUTTON, OnShowButton) ON_COMMAND(IDM_SCORE, OnScore) ON_COMMAND(IDM_SOUND, OnSound) ON_COMMAND(IDM_WELCOME, OnWelcome) ON_BN_CLICKED(IDM_BUTTON, OnPass) ON_WM_CHAR() ON_MESSAGE(WM_PRINTCLIENT, OnPrintClient) ON_WM_CLOSE() ON_WM_CREATE() ON_WM_ERASEBKGND() ON_WM_LBUTTONDOWN() ON_WM_PAINT() END_MESSAGE_MAP() /**************************************************************************** CMainWindow constructor creates green background brush, and main hearts window ****************************************************************************/ CMainWindow::CMainWindow(LPTSTR lpCmdLine) : m_lpCmdLine(lpCmdLine), passdir(LEFT), bCheating(FALSE), bSoundOn(FALSE), bTimerOn(FALSE), bConstructed(TRUE), m_FatalErrno(0), bEnforceFirstBlood(TRUE) { #if !defined (MFC1) m_bAutoMenuEnable = FALSE; // MFC 1.0 compatibility, required for MFC2 #endif ::cQdMoves = 0; // no moves in move queue ::cQdPasses = 0; // no passes either for (int i = 0; i < MAXPLAYER; i++) p[i] = NULL; ResetHandInfo(-1); // set handinfo struct to default values // Check for monochrome CDC ic; ic.CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL); if (ic.GetDeviceCaps(NUMCOLORS) == 2) // if monochrome m_bkgndcolor = RGB(255, 255, 255); // white background for mono else m_bkgndcolor = RGB(0, 127, 0); ic.DeleteDC(); m_BgndBrush.CreateSolidBrush(m_bkgndcolor); // destroyed in OnClose() LoadAccelTable( TEXT("HeartsAccel") ); RECT rc; SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0); CRect rect; int dy = min(WINHEIGHT, (rc.bottom - rc.top)); int x, y; if (GetSystemMetrics(SM_CYSCREEN) <= 480) // VGA { x = (((rc.right - rc.left) - WINWIDTH) / 2) + rc.left; // centered y = rc.top; } else { x = CW_USEDEFAULT; y = CW_USEDEFAULT; } rect.SetRect(x, y, x+WINWIDTH, y+dy); CString sAppname; sAppname.LoadString(IDS_APPNAME); #if defined (WINDOWS_ME) && ! defined (USE_MIRRORING) if (GetSystemMetrics(SM_MIDEASTENABLED)) { char sz[10]; long cb = sizeof(sz); // // as we are releasing an enabled version, we need to check the // resource locale as well. // sz[0] = '\0'; if( RegQueryValue( HKEY_CURRENT_USER, NLS_RESOURCE_LOCALE_KEY, sz, &cb) == ERROR_SUCCESS) if ( (cb == 9) && (sz[6] == '0') && ((sz[7] == '1') || (sz[7] == 'd') || (sz[7] == 'D')) ) { meSystem = TRUE; meMsgBox = MB_RIGHT | MB_RTLREADING; } } #endif #if defined (WINDOWS_ME) && ! defined (USE_MIRRORING) Create( NULL, // default class sAppname, // window title WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPCHILDREN, // window style rect, // size NULL, // parent TEXT("HeartsMenu"), // menu (meSystem ? WS_EX_RTLREADING | WS_EX_RIGHT : 0)); // dwStyle #else Create( NULL, // default class sAppname, // window title WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPCHILDREN, // window style rect, // size NULL, // parent TEXT("HeartsMenu")); // menu #endif } /**************************************************************************** CMainWindow::OnAbout displays about box ****************************************************************************/ //extern "C" int WINAPI ShellAbout(HWND, LPCSTR, LPCSTR, HICON); void CMainWindow::OnAbout() { HICON hIcon = ::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(AFX_IDI_STD_FRAME)); CString s; s.LoadString(IDS_NETWORK); ShellAbout(m_hWnd, s, NULL, hIcon); } /**************************************************************************** CMainWindow::OnQuote displays quote box and plays quote. ****************************************************************************/ void CMainWindow::OnQuote() { CQuoteDlg quote(this); // HeartsPlaySound(SND_QUOTE); quote.DoModal(); HeartsPlaySound(OFF); } /**************************************************************************** CMainWindow::OnChar, looks space, plays first legal move, or pushes button ****************************************************************************/ void CMainWindow::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // We know the cast below is legal because position 0 is always // the local human. local_human *p0 = (local_human *)p[0]; int mode = p0->GetMode(); if ((nChar != (UINT)' ') || (p0->IsTimerOn())) return; if (mode != PLAYING) return; p0->SetMode(WAITING); POINT loc; for (SLOT s = 0; s < MAXSLOT; s++) { if (p0->GetCardLoc(s, loc)) { if (p0->PlayCard(loc.x, loc.y, handinfo, bCheating, FALSE)) { return; } } } p0->SetMode(PLAYING); } /**************************************************************************** CMainWindow::OnCheat -- toggles bCheating used to show all cards face up. ****************************************************************************/ void CMainWindow::OnCheat() { RegEntry Reg(szRegPath); const TCHAR val[] = TEXT("ZB"); TCHAR buf[20]; Reg.GetString(val, buf, sizeof(buf)); if (buf[0] != TEXT('4') || buf[1] != TEXT('2')) return; bCheating = !bCheating; InvalidateRect(NULL, TRUE); // redraw main hearts window CMenu *pMenu = GetMenu(); pMenu->CheckMenuItem(IDM_CHEAT, bCheating ? MF_CHECKED : MF_UNCHECKED); } /**************************************************************************** CMainWindow::OnClose -- cleans up background brush, deletes players, etc. ****************************************************************************/ void CMainWindow::OnClose() { m_BgndBrush.DeleteObject(); for (int i = 0; i < 4; i++) { if (p[i]) { delete p[i]; p[i] = NULL; } } DestroyStrHandles(); if (ddeClient) delete ddeClient; if (ddeServer) delete ddeServer; dde = NULL; ddeClient = NULL; ddeServer = NULL; ::HtmlHelp(::GetDesktopWindow(), szHelpFileName, HH_CLOSE_ALL, 0); { RegEntry Reg(szRegPath); Reg.FlushKey(); } DestroyWindow(); } /**************************************************************************** CMainWindow::OnCreate -- creates pass button child window & player objects. also initializes some of the data members ****************************************************************************/ int CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) { ::pMainWnd = this; if (!bConstructed) { FatalError(IDS_MEMORY); return -1; } // Check for existence of cards.dll SetErrorMode(SEM_NOOPENFILEERRORBOX); HINSTANCE hCardsDLL = LoadLibrary(TEXT("CARDS.DLL")); if (hCardsDLL < (HINSTANCE)HINSTANCE_ERROR) { FatalError(IDS_CARDSDLL); bConstructed = FALSE; return -1; } ::FreeLibrary(hCardsDLL); CClientDC dc(this); TEXTMETRIC tm; ::srand((unsigned) ::time(NULL)); // set rand() seed ddeClient = NULL; ddeServer = NULL; dc.GetTextMetrics(&tm); int nTextHeight = tm.tmHeight + tm.tmExternalLeading; m_StatusHeight = nTextHeight + 11; GetClientRect(m_TableRect); m_TableRect.bottom -= m_StatusHeight; bConstructed = TRUE; // Player 0 is constructed as the gamemeister. This // initializes lots of good stuff which is used later. // If player 0 doesn't happen to be a real gamemeister, // this gets fixed up in OnWelcome(). p[0] = new local_human(0); // display status bar if (p[0] == NULL) { bConstructed = FALSE; return -1; } // Construct pushbutton int cxChar = tm.tmAveCharWidth; int cyChar = tm.tmHeight + tm.tmExternalLeading; int nWidth = (60 * cxChar) / 4; int nHeight = (14 * cyChar) / 8; int x = (m_TableRect.right / 2) - (nWidth / 2); int y = m_TableRect.bottom - card::dyCrd - (2 * POPSPACING) - nHeight; CRect rect; rect.SetRect(x, y, x+nWidth, y+nHeight); if (!m_Button.Create(TEXT(""), WS_CHILD | BS_PUSHBUTTON, rect, this, IDM_BUTTON)) { bConstructed = FALSE; return -1; } // check for sound capability RegEntry Reg(szRegPath); bHasSound = SoundInit(); if (bHasSound) { if (Reg.GetNumber(regvalSound, FALSE)) { CMenu *pMenu = GetMenu(); pMenu->CheckMenuItem(IDM_SOUND, MF_CHECKED); bSoundOn = TRUE; } } else { CMenu *pMenu = GetMenu(); pMenu->EnableMenuItem(IDM_SOUND, MF_GRAYED); } card c; int nStepSize; DWORD dwSpeed = Reg.GetNumber(regvalSpeed, IDC_NORMAL); if (dwSpeed == IDC_FAST) nStepSize = 60; else if (dwSpeed == IDC_SLOW) nStepSize = 5; else nStepSize = 15; c.SetStepSize(nStepSize); return (bConstructed ? 0 : -1); } /**************************************************************************** CMainWindow::OnEraseBkgnd -- required to draw background green ****************************************************************************/ BOOL CMainWindow::OnEraseBkgnd(CDC *pDC) { if (!m_BgndBrush.m_hObject) // if background brush is not valid return FALSE; m_BgndBrush.UnrealizeObject(); CBrush *pOldBrush = pDC->SelectObject(&m_BgndBrush); pDC->PatBlt(0, 0, WINWIDTH, WINHEIGHT, PATCOPY); pDC->SelectObject(pOldBrush); return FALSE; } /**************************************************************************** CMainWindow::OnLButtonDown Handles human selecting card to play or pass. ****************************************************************************/ void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point) { // We know the cast below is legal because position 0 is always // the local human. #ifdef USE_MIRRORING CRect rect; DWORD ProcessDefaultLayout; if (GetProcessDefaultLayout(&ProcessDefaultLayout)) if (ProcessDefaultLayout == LAYOUT_RTL) { GetClientRect(&rect); point.x = rect.right - rect.left - point.x; } #endif local_human *p0 = (local_human *)p[0]; if (p0->IsTimerOn()) // ignore mouse clicks if timer running return; modetype mode = p0->GetMode(); if (mode == SELECTING) { p0->PopCard(m_BgndBrush, point.x, point.y); return; } else if (mode != PLAYING) return; p0->SetMode(WAITING); if (p0->PlayCard(point.x, point.y, handinfo, bCheating)) // valid card? return; // move wasn't legal, so back to PLAYING mode p0->SetMode(PLAYING); } /**************************************************************************** CMainWindow::OnNewGame ****************************************************************************/ void CMainWindow::OnNewGame() { passdir = LEFT; // each new game must start with LEFT bAutostarted = FALSE; // means dealer has agreed to play at least CMenu *pMenu = GetMenu(); pMenu->EnableMenuItem(IDM_NEWGAME, MF_GRAYED); if (role == GAMEMEISTER) { BOOL bNewPlayers = FALSE; for (int i = 1; i < MAXPLAYER; i++) { if (!p[i]) { bNewPlayers = TRUE; p[i] = new computer(i); if (!p[i]) { bConstructed = FALSE; return; } } } if (bNewPlayers) ddeServer->PostAdvise(hszStatus); m_gamenumber = ::rand(); ddeServer->PostAdvise(hszGameNumber); } ResetHandInfo(-1); ::srand(m_gamenumber); { CScoreDlg score(this); score.ResetScore(); } // destruct score TRACE1("\n\ngame number is %d\n\n", m_gamenumber); DUMP(); TRACE0("\n\n"); Shuffle(); } /**************************************************************************** CMainWindow::OnOptions -- user requests options dialog from menu ****************************************************************************/ void CMainWindow::OnOptions() { COptionsDlg optionsdlg(this); optionsdlg.DoModal(); } /**************************************************************************** CMainWindow::OnPaint ****************************************************************************/ void CMainWindow::OnPaint() { CPaintDC dc( this ); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif // players must be painted in order starting with playerled so that // cards in centre overlap correctly if (bConstructed) { int start = Id2Pos(handinfo.playerled % 4); // check that someone has started if (start >= 0) { for (int i = start; i < (MAXPLAYER+start); i++) { int pos = i % 4; if (p[pos]) { if (p[pos]->GetMode() == SCORING) { p[pos]->DisplayHeartsWon(dc); } else { p[pos]->Draw(dc, bCheating); p[pos]->MarkSelectedCards(dc); } } } } } } /**************************************************************************** CMainWindow::OnPass This function handles the local human pressing the button either to pass selected cards or to accept cards passed. ****************************************************************************/ void CMainWindow::OnPass() { if (p[0]->GetMode() == ACCEPTING) // OK (accepting passed cards) { m_Button.ShowWindow(SW_HIDE); m_Button.SetWindowText(TEXT("")); p[0]->SetMode(WAITING); // local human pushed the button CRect rect; p[0]->GetCoverRect(rect); for (SLOT s = 0; s < MAXSLOT; s++) p[0]->Select(s, FALSE); InvalidateRect(&rect, TRUE); UpdateWindow(); FirstMove(); for (int i = 0; i < ::cQdMoves; i++) HandleMove(::moveq[i]); ::cQdMoves = 0; ::cQdPasses = 0; return; } m_Button.EnableWindow(FALSE); p[0]->SetMode(DONE_SELECTING); BOOL bReady = TRUE; for (int i = 1; i < MAXPLAYER; i++) if (p[i]->GetMode() != DONE_SELECTING) bReady = FALSE; if (!bReady) p[0]->UpdateStatus(IDS_PASSWAIT); if (role == GAMEMEISTER) { ddeServer->PostAdvise(hszPass); // let other players know } else { PASS3 pass3; pass3.id = m_myid; pass3.passdir = passdir; p[0]->ReturnSelectedCards(pass3.cardid); ddeClient->Poke(hszPass, &pass3, sizeof(pass3)); } if (bReady) HandlePassing(); } /**************************************************************************** CMainWindow::OnRef After a human or a computer plays a card, they must PostMessage(WM_COMMAND, IDM_REF) which causes this routine (the referee) to be called. Ref does the following: - updates handinfo data struct - calls HeartsPlaySound() if appropriate - determines if the hand is over or, if not, whose turn is next ****************************************************************************/ void CMainWindow::OnRef() { card *c = handinfo.cardplayed[handinfo.turn]; if (!handinfo.bHeartsBroken) { if (c->Suit() == HEARTS) { handinfo.bHeartsBroken = TRUE; HeartsPlaySound(SND_BREAK); } } if (c->ID() == BLACKLADY) { handinfo.bQSPlayed = TRUE; HeartsPlaySound(SND_QUEEN); } /* ------------------------------------------------ #if defined(_DEBUG) TRACE("[%d] ", m_myid); TRACE("h.turn %d, ", handinfo.turn); TRACE("led %d, ", handinfo.playerled); for (int i = 0; i < 4; i++) { if (handinfo.cardplayed[i]) { CDNAME(handinfo.cardplayed[i]); } else { TRACE("-- "); } } TRACE("\n",); #endif ------------------------------------------------ */ int pos = Id2Pos(handinfo.turn); SLOT slot = p[pos]->GetSlot(handinfo.cardplayed[handinfo.turn]->ID()); #if defined(_DEBUG) if (p[pos]->IsHuman()) ((human *)p[pos])->DebugMove(slot); #endif p[pos]->GlideToCentre(slot, pos==0 ? TRUE : bCheating); handinfo.turn++; handinfo.turn %= 4; int newpos = Id2Pos(handinfo.turn); if (handinfo.turn == handinfo.playerled) { EndHand(); } else { p[newpos]->SelectCardToPlay(handinfo, bCheating); if (newpos != 0) ((local_human *)p[0])->WaitMessage(p[newpos]->GetName()); } } /**************************************************************************** CMainWindow::OnScore -- user requests score dialog from menu ****************************************************************************/ void CMainWindow::OnScore() { CScoreDlg scoredlg(this); // this constructor does not add new info scoredlg.DoModal(); } /**************************************************************************** CMainWindow::DoSort ****************************************************************************/ void CMainWindow::DoSort() { for (int i = 0; i < (bCheating ? MAXPLAYER : 1); i++) { CRect rect; int id; // card in play for this player if (handinfo.cardplayed[i] == NULL) id = EMPTY; else id = handinfo.cardplayed[i]->ID(); p[i]->Sort(); if (id != EMPTY) // if this player has a card in play, restore it { for (SLOT s = 0; s < MAXSLOT; s++) { if (p[i]->GetID(s) == id) { handinfo.cardplayed[i] = p[i]->Card(s); break; } } } p[i]->GetCoverRect(rect); InvalidateRect(&rect, TRUE); } } /**************************************************************************** CMainWindow::OnSound() request sound on or off from menu. ****************************************************************************/ void CMainWindow::OnSound() { RegEntry Reg(szRegPath); bSoundOn = !bSoundOn; CMenu *pMenu = GetMenu(); pMenu->CheckMenuItem(IDM_SOUND, bSoundOn ? MF_CHECKED : MF_UNCHECKED); if (bSoundOn) Reg.SetValue(regvalSound, 1); else Reg.DeleteValue(regvalSound); } /**************************************************************************** CMainWindow::OnPrintClient() Draw background into the specified HDC. This is used when drawing the "Pass" button in the Luna style. ****************************************************************************/ LRESULT CMainWindow::OnPrintClient(WPARAM wParam, LPARAM lParam) { CDC dc; CRect rect; dc.Attach((HDC)wParam); GetClientRect(&rect); dc.FillRect(&rect, &m_BgndBrush); dc.Detach(); return 1; }