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