/***************************************************************************/ /** Microsoft Windows **/ /** Copyright(c) Microsoft Corp., 1991, 1992 **/ /***************************************************************************/ /**************************************************************************** ddecb.cpp Aug 92, JimH May 93, JimH chico port DDE callback functions are here, as well as some helper functions. The callbacks quickly call CMainWindow member function equivalents. ****************************************************************************/ #include "hearts.h" #include "main.h" #include "debug.h" #include "resource.h" /**************************************************************************** DdeServerCallback() ****************************************************************************/ HDDEDATA EXPENTRY EXPORT DdeServerCallBack(WORD wType, WORD wFmt, HCONV hConv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2) { return ::pMainWnd->DdeSrvCallBack(wType, wFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2); } HDDEDATA CMainWindow::DdeSrvCallBack( WORD wType, // transaction type WORD wFmt, // clipboard format HCONV hConv, // handle to conversation HSZ hsz1, // string handles HSZ hsz2, HDDEDATA hData, // handle to a global memory object DWORD dwData1, // transaction-specific data DWORD dwData2) { switch(wType) { case XTYP_ADVREQ: // server called PostAdvise if (hsz2 == hszStatus) // if status update { return GetGameStatus(hConv); } else if (hsz2 == hszGameNumber) { int num = GetGameNumber(); return dde->CreateDataHandle(&num, sizeof(num), hszGameNumber); } else if (hsz2 == hszPass) // update pass selections all players { PASS12 pass12; GetPass12(pass12); return dde->CreateDataHandle(&pass12, sizeof(pass12), hszPass); } else if (hsz2 == hszMove) { return dde->CreateDataHandle(&::move, sizeof(::move), hszMove); } else return (HDDEDATA) NULL; case XTYP_ADVSTART: // client requested advise loop if (hsz2 == hszGameNumber) { if (CountClients() == 3) PostMessage(WM_COMMAND, IDM_NEWGAME); } return (HDDEDATA) TRUE; case XTYP_ADVSTOP: return (HDDEDATA) NULL; case XTYP_CONNECT: // return TRUE to accept connection return (HDDEDATA) TRUE; case XTYP_CONNECT_CONFIRM: return (HDDEDATA) NULL; case XTYP_DISCONNECT: { int id; if (IsCurrentPlayer(hConv, &id)) PlayerQuit(id); } return (HDDEDATA) NULL; case XTYP_ERROR: return (HDDEDATA) NULL; case XTYP_EXECUTE: return (HDDEDATA) NULL; case XTYP_POKE: if (hsz2 == hszJoin) { // add new player and inform others of new arrival if (CountClients() < 3) { AddNewPlayer(hConv, hData); ddeServer->PostAdvise(hszStatus); return (HDDEDATA) DDE_FACK; } else return (HDDEDATA) DDE_FBUSY; } else if (hsz2 == hszPass) { // client notifies server of 3 cards to pass ReceivePassFromClient(hData); HandlePassing(); return (HDDEDATA) DDE_FACK; } else if (hsz2 == hszMove) { // client informs server of card played ddeServer->GetData(hData, (PBYTE)&::move, sizeof(::move)); ddeServer->PostAdvise(hszMove); ReceiveMove(::move); return (HDDEDATA) DDE_FACK; } else if (hsz2 == hszPassUpdate) { // Client just shuffled and wants to know who has passed so far. // Be sure we're not still looking at score. int mode = GetPlayerMode(0); if (mode == SELECTING || mode == DONE_SELECTING) ddeServer->PostAdvise(hszPass); } else return (HDDEDATA) DDE_FNOTPROCESSED; case XTYP_REGISTER: return (HDDEDATA) NULL; case XTYP_REQUEST: // when client first joins game, he asks for update on names // of other players already in the game if (hsz2 == hszStatus) return GetGameStatus(hConv); else return (HDDEDATA) NULL; case XTYP_UNREGISTER: return (HDDEDATA) NULL; case XTYP_WILDCONNECT: return (HDDEDATA) NULL; default: return (HDDEDATA) NULL; } } /**************************************************************************** DdeClientCallback() ****************************************************************************/ HDDEDATA EXPENTRY EXPORT DdeClientCallBack(WORD wType, WORD wFmt, HCONV hConv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2) { return ::pMainWnd->DdeCliCallBack(wType, wFmt, hConv, hsz1, hsz2, hData, dwData1, dwData2); } HDDEDATA CMainWindow::DdeCliCallBack( WORD wType, // transaction type WORD wFmt, // clipboard format HCONV hConv, // handle to conversation HSZ hsz1, // string handles HSZ hsz2, HDDEDATA hData, // handle to a global memory object DWORD dwData1, // transaction-specific data DWORD dwData2) { switch(wType) { case XTYP_ADVDATA: // advdata is sent whenever the server posts an advise // on topics client has set up an advise loop on if (hsz2 == hszStatus) { // someone new joined, and here's the names GAMESTATUS gs; ddeClient->GetData(hData, (PBYTE)&gs, sizeof(gs)); UpdateStatusNames(gs); return (HDDEDATA) DDE_FACK; } else if (hsz2 == hszGameNumber) { // OnNewGame called, here's the game number int num; ddeClient->GetData(hData, (PBYTE)&num, sizeof(num)); // Future versions of Hearts can use a negative number // in the gamenumber field to indicate a version. if (num < 0) FatalError(IDS_VERSION); SetGameNumber(num); OnNewGame(); return (HDDEDATA) DDE_FACK; } else if (hsz2 == hszPass) { // someone has passed, here's the update on everyone PASS12 pass12; ddeClient->GetData(hData, (PBYTE)&pass12, sizeof(pass12)); UpdatePassStatus(pass12); HandlePassing(); return (HDDEDATA) DDE_FACK; } else if (hsz2 == hszMove) { // someone has moved MOVE moveLocal; ddeClient->GetData(hData, (PBYTE)&moveLocal, sizeof(moveLocal)); ReceiveMove(moveLocal); return (HDDEDATA) DDE_FACK; } else return (HDDEDATA) NULL; case XTYP_DISCONNECT: FatalError(IDS_DISCONNECT); return (HDDEDATA) NULL; case XTYP_ERROR: return (HDDEDATA) NULL; case XTYP_UNREGISTER: return (HDDEDATA) NULL; case XTYP_XACT_COMPLETE: return (HDDEDATA) NULL; default: return (HDDEDATA) NULL; } } /**************************************************************************** ****************************************************************************/ // Server helper functions /**************************************************************************** CMainWindow::AddNewPlayer() After the server gets a valid request from a new player to join the game, this function is called to create the new player object. ****************************************************************************/ int CMainWindow::AddNewPlayer(HCONV hConv, HDDEDATA hData) { int id; // only gamemeister calls this, so pos == id if (!p[2]) // new players get added in order 2, 1, 3 id = 2; else if (!p[1]) id = 1; else id = 3; p[id] = new remote_human(id, id, hConv); if (p[id] == NULL) { FatalError(IDS_MEMORY); return id; } CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif CString name = ddeServer->GetDataString(hData); p[id]->SetName(name, dc); p[id]->DisplayName(dc); return id; } /**************************************************************************** CMainWindow::GetPass12() This function gets called in response to the server calling PostAdvise(hszPass). That happens when the gamemeister passes cards, when the gamemeister learns that another player has passed cards, or when a client request an update via Poke(hszPassUpdate). ****************************************************************************/ void CMainWindow::GetPass12(PASS12& pass12) { pass12.passdir = passdir; for (int pos = 0; pos < MAXPLAYER; pos++) p[Pos2Id(pos)]->ReturnSelectedCards(pass12.cardid[pos]); } /**************************************************************************** CMainWindow::ReceivePassFromClient() Called in reponse to a client poking hszPass info. ****************************************************************************/ void CMainWindow::ReceivePassFromClient(HDDEDATA hData) { PASS3 pass3; ddeServer->GetData(hData, (PBYTE)&pass3, sizeof(pass3)); // queue up pass info if gamemeister is still looking at score if (p[0]->GetMode() == SCORING) { ::passq[::cQdPasses++] = pass3; return; } else if (::cQdMoves > 0) { for (int i = 0; i < ::cQdPasses; i++) HandlePass(::passq[i]); ::cQdPasses = 0; } HandlePass(pass3); } /**************************************************************************** CMainWindow::HandlePass() Called from ReceivePassFromClient() and also DispatchCards() in case any passes were queued up when the gamemeister was looking at the score. ****************************************************************************/ void CMainWindow::HandlePass(PASS3& pass3) { CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif int pos = pass3.id; // only gamemeister queues moves for (int i = 0; i < 3; i++) { SLOT s = p[pos]->GetSlot(pass3.cardid[i]); p[pos]->Select(s, TRUE); } p[pos]->SetMode(DONE_SELECTING); p[pos]->MarkSelectedCards(dc); ddeServer->PostAdvise(hszPass); } /**************************************************************************** CMainWindow::IsCurrentPlayer() The gamemeister uses this function to determine if an XTYP_DISCONNECT message comes from a currently active player, or whether it can just be ignored. ****************************************************************************/ BOOL CMainWindow::IsCurrentPlayer(HCONV hConv, int *id) { BOOL bResult = FALSE; for (int i = 1; i < MAXPLAYER; i++) if (p[i]) if (hConv == p[i]->GetConv()) { bResult = TRUE; *id = i; } return bResult; } /**************************************************************************** CMainWindow::GetGameStatus() Returns a dde handle with current active players' names and ids. ****************************************************************************/ HDDEDATA CMainWindow::GetGameStatus(HCONV hConv) { GAMESTATUS gs; gs.id = -1; // error value if not reset for (int i = 0; i < MAXPLAYER; i++) { if (p[i]) { lstrcpy(gs.name[i], p[i]->GetName()); if (p[i]->GetConv() == hConv) { gs.id = i; } } else gs.name[i][0] = '\0'; } return dde->CreateDataHandle(&gs, sizeof(gs), hszStatus); } /**************************************************************************** CMainWindow::UpdatePassStatus() Fills a PASS12 structure with current pass information. ****************************************************************************/ void CMainWindow::UpdatePassStatus(PASS12& pass12) { CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif for (int id = 0; id < MAXPLAYER; id++) { if (pass12.cardid[id][0] != EMPTY) { int pos = Id2Pos(id); if (pos != 0) // if it's not me { if (p[pos]->GetMode() == SELECTING) { for (int i = 0; i < 3; i++) { SLOT slot = p[pos]->GetSlot(pass12.cardid[id][i]); p[pos]->Select(slot, TRUE); } p[pos]->MarkSelectedCards(dc); p[pos]->SetMode(DONE_SELECTING); } } } } } /**************************************************************************** CMainWindow::PlayerQuit() The gamemeister has been notified that a currently active player has disconnected. ****************************************************************************/ void CMainWindow::PlayerQuit(int id) { // If hearts is running only because it has been autostarted, and // the autostarted leaves, you might as well shut down. Note that // bAutostarted is set to FALSE as soon as the dealer actually // starts a game. if (bAutostarted && (id == 2)) PostMessage(WM_CLOSE); p[id]->Quit(); // mark this player as quitting modetype mode = p[id]->GetMode(); // change name to [name] CString name = p[id]->GetName(); CString newname = "[" + name + "]"; CClientDC dc(this); #ifdef USE_MIRRORING SetLayout(dc.m_hDC, 0); SetLayout(dc.m_hAttribDC, 0); #endif p[id]->SetName(newname, dc); p[id]->DisplayName(dc); if (mode == SELECTING) { p[id]->SelectCardsToPass(); ddeServer->PostAdvise(hszPass); // let others put up white dots BOOL bReady = TRUE; // time to ungray pass button? for (int i = 1; i < MAXPLAYER; i++) if (p[i]->GetMode() != DONE_SELECTING) bReady = FALSE; if (bReady) HandlePassing(); } else if (mode == PLAYING) { p[id]->SelectCardToPlay(handinfo, bCheating); } ddeServer->PostAdvise(hszStatus); // let others know quitter is [quitter] } /**************************************************************************** ****************************************************************************/ // Helper functions used by both server and client /**************************************************************************** CMainWindow::ReceiveMove() Move data has been received. Determine if the move can be handled now or if it must be queued. ****************************************************************************/ void CMainWindow::ReceiveMove(MOVE& move) { if (move.playerid == m_myid) // don't have to handle my own moves return; int mode = p[0]->GetMode(); if (mode == ACCEPTING || mode == SCORING || bTimerOn) { ::moveq[::cQdMoves++] = move; return; } else if (::cQdMoves > 0) { for (int i = 0; i < ::cQdMoves; i++) HandleMove(::moveq[i]); ::cQdMoves = 0; } HandleMove(move); } /**************************************************************************** CMainWindow::Handle Move() This is called either from ReceiveMove() or from some other function that has temporarily suspended move processing and is now clearing out its move queue. ****************************************************************************/ void CMainWindow::HandleMove(MOVE& move) { int pos = Id2Pos(move.playerid); SLOT s = p[pos]->GetSlot(move.cardid); ASSERT(s >= 0); ASSERT(s < MAXSLOT); ASSERT(p[pos]->Card(s)->IsValid()); p[pos]->SetMode(WAITING); p[pos]->MarkCardPlayed(s); handinfo.cardplayed[move.playerid] = p[pos]->Card(s); OnRef(); }