/* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */ /* * Copyright 2001-2011 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "game.h" #include "dictnry.h" #include "strutils.h" #include "nli.h" #include "dbgutil.h" #ifdef CPLUS extern "C" { #endif #define FLAG_HASCOMMS 0x01 #ifdef DEBUG static void assertUtilOK( XW_UtilCtxt* util ) { UtilVtable* vtable; XP_U16 nSlots; XP_ASSERT( !!util ); vtable = util->vtable; nSlots = sizeof(vtable) / 4; while ( nSlots-- ) { void* fptr = ((void**)vtable)[nSlots]; XP_ASSERT( !!fptr ); } } /* assertUtilOK */ #else # define assertUtilOK(u) #endif #ifdef XWFEATURE_CHANGEDICT static void gi_setDict( MPFORMAL CurGameInfo* gi, const DictionaryCtxt* dict ); #endif static void checkServerRole( CurGameInfo* gi, XP_U16* nPlayersHere, XP_U16* nPlayersTotal ) { if ( !!gi ) { XP_U16 ii, remoteCount = 0; if ( SERVER_STANDALONE != gi->serverRole ) { for ( ii = 0; ii < gi->nPlayers; ++ii ) { if ( !gi->players[ii].isLocal ) { ++remoteCount; } } /* I think this error is caught in nwgamest.c now */ XP_ASSERT( remoteCount > 0 ); if ( remoteCount == 0 ) { gi->serverRole = SERVER_STANDALONE; } } *nPlayersHere = gi->nPlayers - remoteCount; *nPlayersTotal = gi->nPlayers; } } /* checkServerRole */ static XP_U32 makeGameID( XW_UtilCtxt* XP_UNUSED_DBG(util) ) { XP_U32 gameID = 0; assertUtilOK( util ); while ( 0 == gameID ) { /* High bit never set by XP_RANDOM() alone */ gameID = (XP_RANDOM() << 16) ^ XP_RANDOM(); /* But let's clear it -- set high-bit causes problems for existing postgres DB where INTEGER is apparently a signed 32-bit */ gameID &= 0x7FFFFFFF; } LOG_RETURNF( "%X/%d", gameID, gameID ); return gameID; } static void timerChangeListener( XWEnv xwe, void* data, const XP_U32 gameID, XP_S32 oldVal, XP_S32 newVal ) { XWGame* game = (XWGame*)data; XP_ASSERT( game->util->gameInfo->gameID == gameID ); XP_LOGF( "%s(oldVal=%d, newVal=%d, id=%d)", __func__, oldVal, newVal, gameID ); dutil_onDupTimerChanged( util_getDevUtilCtxt( game->util, xwe ), xwe, gameID, oldVal, newVal ); } static void onRoleChanged( XWEnv xwe, void* closure, XP_Bool amNowGuest ) { XP_ASSERT( amNowGuest ); XWGame* game = (XWGame*)closure; server_onRoleChanged( game->server, xwe, amNowGuest ); } static void setListeners( XWGame* game, const CommonPrefs* cp ) { server_prefsChanged( game->server, cp ); board_prefsChanged( game->board, cp ); server_setTimerChangeListener( game->server, timerChangeListener, game ); } static const DictionaryCtxt* getDicts( const CurGameInfo* gi, XW_UtilCtxt* util, XWEnv xwe, PlayerDicts* playerDicts ) { const XP_UCHAR* isoCode = gi->isoCodeStr; const DictionaryCtxt* result = util_getDict( util, xwe, isoCode, gi->dictName ); XP_MEMSET( playerDicts, 0, sizeof(*playerDicts) ); if ( !!result ) { for ( int ii = 0; ii < gi->nPlayers; ++ii ) { const LocalPlayer* lp = &gi->players[ii]; if ( lp->isLocal && !!lp->dictName && lp->dictName[0] ) { playerDicts->dicts[ii] = util_getDict( util, xwe, isoCode, lp->dictName ); } } } return result; } static void unrefDicts( XWEnv xwe, const DictionaryCtxt* dict, PlayerDicts* playerDicts ) { if ( !!dict ) { dict_unref( dict, xwe ); } for ( int ii = 0; ii < VSIZE(playerDicts->dicts); ++ii ) { const DictionaryCtxt* dict = playerDicts->dicts[ii]; if ( !!dict ) { dict_unref( dict, xwe ); } } } XP_Bool game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi, const CommsAddrRec* selfAddr, const CommsAddrRec* hostAddr, XW_UtilCtxt* util, DrawCtx* draw, const CommonPrefs* cp, const TransportProcs* procs #ifdef SET_GAMESEED ,XP_U16 gameSeed #endif ) { XP_ASSERT( gi == util->gameInfo ); /* if holds, remove gi param */ #ifndef XWFEATURE_STANDALONE_ONLY XP_U16 nPlayersHere = 0; XP_U16 nPlayersTotal = 0; checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); #endif assertUtilOK( util ); if ( 0 == gi->gameID ) { gi->gameID = makeGameID( util ); } game->created = dutil_getCurSeconds( util_getDevUtilCtxt( util, xwe ), xwe ); game->util = util; PlayerDicts playerDicts; const DictionaryCtxt* dict = getDicts( gi, util, xwe, &playerDicts ); XP_Bool success = !!dict; if ( success ) { XP_STRNCPY( gi->isoCodeStr, dict_getISOCode( dict ), VSIZE(gi->isoCodeStr) ); XP_ASSERT( !!gi->isoCodeStr[0] ); game->model = model_make( MPPARM(mpool) xwe, (DictionaryCtxt*)NULL, NULL, util, gi->boardSize ); model_setDictionary( game->model, xwe, dict ); model_setPlayerDicts( game->model, xwe, &playerDicts ); #ifndef XWFEATURE_STANDALONE_ONLY if ( gi->serverRole != SERVER_STANDALONE ) { game->comms = comms_make( MPPARM(mpool) xwe, util, gi->serverRole != SERVER_ISCLIENT, selfAddr, hostAddr, #ifdef XWFEATURE_RELAY nPlayersHere, nPlayersTotal, #endif procs, onRoleChanged, game, gi->forceChannel #ifdef SET_GAMESEED , gameSeed #endif ); } else { game->comms = (CommsCtxt*)NULL; } #endif game->server = server_make( MPPARM(mpool) xwe, game->model, #ifndef XWFEATURE_STANDALONE_ONLY game->comms, #else (CommsCtxt*)NULL, #endif util ); game->board = board_make( MPPARM(mpool) xwe, game->model, game->server, NULL, util ); board_setCallbacks( game->board, xwe ); board_setDraw( game->board, xwe, draw ); setListeners( game, cp ); } unrefDicts( xwe, dict, &playerDicts ); return success; } /* game_makeNewGame */ XP_Bool game_makeRematch( const XWGame* oldGame, XWEnv xwe, XW_UtilCtxt* newUtil, const CommonPrefs* newCp, const TransportProcs* procs, XWGame* newGame, const XP_UCHAR* newName ) { XP_Bool success = XP_FALSE; XP_LOGFF( "(newName=%s)", newName ); RematchAddrs ra; if ( server_getRematchInfo( oldGame->server, newUtil, makeGameID( newUtil ), &ra ) ) { CommsAddrRec* selfAddrP = NULL; CommsAddrRec selfAddr; if ( !!oldGame->comms ) { comms_getSelfAddr( oldGame->comms, &selfAddr ); selfAddrP = &selfAddr; } if ( game_makeNewGame( MPPARM(newUtil->mpool) xwe, newGame, newUtil->gameInfo, selfAddrP, (CommsAddrRec*)NULL, newUtil, (DrawCtx*)NULL, newCp, procs ) ) { const CurGameInfo* newGI = newUtil->gameInfo; for ( int ii = 0; ii < ra.nAddrs; ++ii ) { NetLaunchInfo nli; /* hard-code one player per device -- for now */ nli_init( &nli, newGI, selfAddrP, 1, ii + 1 ); if ( !!newName ) { nli_setGameName( &nli, newName ); } LOGNLI( &nli ); comms_invite( newGame->comms, xwe, &nli, &ra.addrs[ii], XP_TRUE ); } success = XP_TRUE; } } LOG_RETURNF( "%s", boolToStr(success) ); return success; } #ifdef XWFEATURE_CHANGEDICT void game_changeDict( MPFORMAL XWGame* game, XWEnv xwe, CurGameInfo* gi, DictionaryCtxt* dict ) { model_setDictionary( game->model, xwe, dict ); gi_setDict( MPPARM(mpool) gi, dict ); server_resetEngines( game->server ); } #endif XP_Bool game_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp, const TransportProcs* procs ) { XP_ASSERT( NULL == util || gi == util->gameInfo ); XP_Bool success = XP_FALSE; XP_U8 strVersion; #ifndef XWFEATURE_STANDALONE_ONLY XP_Bool hasComms; #endif strVersion = stream_getU8( stream ); XP_LOGFF( "strVersion = 0x%x", (XP_U16)strVersion ); if ( strVersion > CUR_STREAM_VERS ) { XP_LOGFF( "aborting; stream version too new (%d > %d)!", strVersion, CUR_STREAM_VERS ); } else { do { /* do..while so can break */ stream_setVersion( stream, strVersion ); gi_readFromStream( MPPARM(mpool) stream, gi ); if ( !game ) { success = XP_TRUE; break; } else if ( stream_getSize(stream) == 0 ) { XP_LOGFF( "gi was all we got; failing." ); break; } game->util = util; game->created = strVersion < STREAM_VERS_GICREATED ? 0 : stream_getU32( stream ); PlayerDicts playerDicts; const DictionaryCtxt* dict = getDicts( gi, util, xwe, &playerDicts ); if ( !dict ) { break; } /* Previous stream versions didn't save anything if built * standalone. Now we always save something. But we need to know * if the previous version didn't save. PREV_WAS_STANDALONE_ONLY * tells us that. */ hasComms = XP_FALSE; if ( STREAM_VERS_ALWAYS_MULTI <= strVersion /* new stream */ #ifndef PREV_WAS_STANDALONE_ONLY || XP_TRUE /* old, but saved this anyway */ #endif ) { if ( strVersion < STREAM_VERS_GICREATED ) { hasComms = stream_getU8( stream ); } else { XP_U8 flags = stream_getU8( stream ); hasComms = flags & FLAG_HASCOMMS; } } XP_ASSERT( hasComms == (SERVER_STANDALONE != gi->serverRole) ); if ( hasComms ) { game->comms = comms_makeFromStream( MPPARM(mpool) xwe, stream, util, gi->serverRole != SERVER_ISCLIENT, procs, onRoleChanged, game, gi->forceChannel ); } else { XP_ASSERT( NULL == game->comms ); game->comms = NULL; } game->model = model_makeFromStream( MPPARM(mpool) xwe, stream, dict, &playerDicts, util ); game->server = server_makeFromStream( MPPARM(mpool) xwe, stream, game->model, game->comms, util, gi->nPlayers ); game->board = board_makeFromStream( MPPARM(mpool) xwe, stream, game->model, game->server, NULL, util, gi->nPlayers ); setListeners( game, cp ); board_setDraw( game->board, xwe, draw ); success = XP_TRUE; unrefDicts( xwe, dict, &playerDicts ); } while( XP_FALSE ); } if ( success && !!game && !!game->comms ) { XP_ASSERT( comms_getIsServer(game->comms) == server_getIsServer(game->server) ); #ifdef XWFEATURE_KNOWNPLAYERS const XP_U32 created = game->created; if ( 0 != created && server_getGameIsConnected( game->server ) ) { comms_gatherPlayers( game->comms, xwe, created ); } #endif } return success; } /* game_makeFromStream */ /* This is a gross hack. Fix it someday. */ static void runServer( ServerCtxt* server, XWEnv xwe ) { for ( int ii = 0; ii < 5; ++ii ) { (void)server_do( server, xwe ); } } XP_Bool game_makeFromInvite( XWGame* newGame, XWEnv xwe, const NetLaunchInfo* nli, const CommsAddrRec* selfAddr, XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp, const TransportProcs* procs ) { LOG_FUNC(); XP_U32 gameID = nli->gameID; XP_U8 forceChannel = nli->forceChannel; XW_DUtilCtxt* duc = util_getDevUtilCtxt( util, xwe ); XP_Bool success = !dutil_haveGame( duc, xwe, gameID, forceChannel ); if ( success ) { CurGameInfo* gi = util->gameInfo; XP_ASSERT( !!gi ); nliToGI( nli, xwe, util, gi ); CommsAddrRec hostAddr; nli_makeAddrRec( nli, &hostAddr ); success = game_makeNewGame( MPPARM(util->mpool) xwe, newGame, gi, selfAddr, &hostAddr, util, draw, cp, procs ); if ( success && server_initClientConnection( newGame->server, xwe ) ) { runServer( newGame->server, xwe ); } } LOG_RETURNF( "%s", boolToStr(success) ); return success; } void game_saveToStream( const XWGame* game, const CurGameInfo* gi, XWStreamCtxt* stream, XP_U16 saveToken ) { XP_ASSERT( gi_equal( gi, game->util->gameInfo ) ); stream_putU8( stream, CUR_STREAM_VERS ); stream_setVersion( stream, CUR_STREAM_VERS ); gi_writeToStream( stream, gi ); if ( !!game ) { const XP_U32 created = game->created; stream_putU32( stream, created ); XP_ASSERT( 0 != saveToken ); XP_U8 flags = NULL == game->comms ? 0 : FLAG_HASCOMMS; stream_putU8( stream, flags ); #ifdef XWFEATURE_STANDALONE_ONLY XP_ASSERT( !game->comms ); #endif if ( NULL != game->comms ) { comms_writeToStream( game->comms, stream, saveToken ); } model_writeToStream( game->model, stream ); server_writeToStream( game->server, stream ); board_writeToStream( game->board, stream ); } } /* game_saveToStream */ void game_saveSucceeded( const XWGame* game, XWEnv xwe, XP_U16 saveToken ) { if ( !!game->comms ) { comms_saveSucceeded( game->comms, xwe, saveToken ); } } XP_Bool game_receiveMessage( XWGame* game, XWEnv xwe, XWStreamCtxt* stream, const CommsAddrRec* retAddr ) { ServerCtxt* server = game->server; CommsMsgState commsState; XP_Bool result = NULL != game->comms; if ( result ) { result = comms_checkIncomingStream( game->comms, xwe, stream, retAddr, &commsState ); } else { XP_LOGFF( "ERROR: comms NULL!" ); } if ( result ) { (void)server_do( server, xwe ); result = server_receiveMessage( server, xwe, stream ); } comms_msgProcessed( game->comms, xwe, &commsState, !result ); if ( result ) { /* in case MORE work's pending. Multiple calls are required in at least one case, where I'm a host handling client registration *AND* I'm a robot. Only one server_do and I'll never make that first robot move. That's because comms can't detect a duplicate initial packet (in validateInitialMessage()). */ runServer( server, xwe ); } LOG_RETURNF( "%s", boolToStr(result) ); return result; } void game_getState( const XWGame* game, XWEnv xwe, GameStateInfo* gsi ) { const ServerCtxt* server = game->server; BoardCtxt* board = game->board; XP_Bool gameOver = server_getGameIsOver( server ); gsi->curTurnSelected = board_curTurnSelected( board ); gsi->trayVisState = board_getTrayVisState( board ); gsi->visTileCount = board_visTileCount( board ); gsi->canHint = !gameOver && board_canHint( board ); gsi->canUndo = model_canUndo( game->model ); gsi->canRedo = board_canTogglePending( board ); gsi->inTrade = board_inTrade( board, &gsi->tradeTilesSelected ); gsi->canChat = !!game->comms && comms_canChat( game->comms ); gsi->canShuffle = board_canShuffle( board ); gsi->canHideRack = board_canHideRack( board ); gsi->canTrade = board_canTrade( board, xwe ); gsi->nPendingMessages = !!game->comms ? comms_countPendingPackets(game->comms, NULL) : 0; gsi->canRematch = server_canRematch( server ); gsi->canPause = server_canPause( server ); gsi->canUnpause = server_canUnpause( server ); } void game_summarize( const XWGame* game, const CurGameInfo* gi, GameSummary* summary ) { XP_MEMSET( summary, 0, sizeof(*summary) ); ServerCtxt* server = game->server; summary->turn = server_getCurrentTurn( server, &summary->turnIsLocal ); summary->lastMoveTime = server_getLastMoveTime(server); XP_STRNCPY( summary->isoCodeStr, gi->isoCodeStr, VSIZE(summary->isoCodeStr)-1 ); summary->gameOver = server_getGameIsOver( server ); summary->nMoves = model_getNMoves( game->model ); summary->dupTimerExpires = server_getDupTimerExpires( server ); for ( int ii = 0; ii < gi->nPlayers; ++ii ) { const LocalPlayer* lp = &gi->players[ii]; if ( LP_IS_ROBOT(lp) || !LP_IS_LOCAL(lp) ) { if ( '\0' != summary->opponents[0] ) { XP_STRCAT( summary->opponents, ", " ); } XP_STRCAT( summary->opponents, lp->name ); } } if ( !!game->comms ) { summary->missingPlayers = server_getMissingPlayers( server ); summary->nPacketsPending = comms_countPendingPackets( game->comms, &summary->quashed ); summary->channelNo = gi->forceChannel; } } XP_Bool game_getIsServer( const XWGame* game ) { XP_Bool result = comms_getIsServer( game->comms ); return result; } void game_dispose( XWGame* game, XWEnv xwe ) { #ifdef XWFEATURE_KNOWNPLAYERS const XP_U32 created = game->created; if ( !!game->comms && 0 != created && server_getGameIsConnected( game->server ) ) { comms_gatherPlayers( game->comms, xwe, created ); } #endif /* The board should be reused!!! PENDING(ehouse) */ if ( !!game->board ) { board_destroy( game->board, xwe, XP_TRUE ); game->board = NULL; } #ifndef XWFEATURE_STANDALONE_ONLY if ( !!game->comms ) { comms_stop( game->comms #ifdef XWFEATURE_RELAY , xwe #endif ); comms_destroy( game->comms, xwe ); game->comms = NULL; } #endif if ( !!game->model ) { model_destroy( game->model, xwe ); game->model = NULL; } if ( !!game->server ) { server_destroy( game->server ); game->server = NULL; } } /* game_dispose */ static void disposePlayerInfoInt( MPFORMAL CurGameInfo* gi ) { for ( int ii = 0; ii < VSIZE(gi->players); ++ii ) { LocalPlayer* lp = &gi->players[ii]; XP_FREEP( mpool, &lp->name ); XP_FREEP( mpool, &lp->password ); XP_FREEP( mpool, &lp->dictName ); } } /* disposePlayerInfoInt */ void gi_disposePlayerInfo( MPFORMAL CurGameInfo* gi ) { disposePlayerInfoInt( MPPARM(mpool) gi ); XP_FREEP( mpool, &gi->dictName ); } /* gi_disposePlayerInfo */ void gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI ) { replaceStringIfDifferent( mpool, &destGI->dictName, srcGI->dictName ); XP_STRNCPY( destGI->isoCodeStr, srcGI->isoCodeStr, VSIZE(destGI->isoCodeStr)-1 ); destGI->gameID = srcGI->gameID; destGI->gameSeconds = srcGI->gameSeconds; destGI->nPlayers = (XP_U8)srcGI->nPlayers; XP_U16 nPlayers = srcGI->nPlayers; destGI->boardSize = (XP_U8)srcGI->boardSize; destGI->traySize = srcGI->traySize; destGI->bingoMin = srcGI->bingoMin; destGI->serverRole = srcGI->serverRole; destGI->hintsNotAllowed = srcGI->hintsNotAllowed; destGI->timerEnabled = srcGI->timerEnabled; destGI->phoniesAction = srcGI->phoniesAction; destGI->allowPickTiles = srcGI->allowPickTiles; destGI->forceChannel = srcGI->forceChannel; destGI->inDuplicateMode = srcGI->inDuplicateMode; XP_LOGF( "%s: copied forceChannel: %d; inDuplicateMode: %d", __func__, destGI->forceChannel, destGI->inDuplicateMode ); const LocalPlayer* srcPl; LocalPlayer* destPl; int ii; for ( srcPl = srcGI->players, destPl = destGI->players, ii = 0; ii < nPlayers; ++srcPl, ++destPl, ++ii ) { replaceStringIfDifferent( mpool, &destPl->name, srcPl->name ); replaceStringIfDifferent( mpool, &destPl->password, srcPl->password ); replaceStringIfDifferent( mpool, &destPl->dictName, srcPl->dictName ); destPl->secondsUsed = srcPl->secondsUsed; destPl->robotIQ = srcPl->robotIQ; destPl->isLocal = srcPl->isLocal; } } /* gi_copy */ #ifdef DEBUG static XP_Bool strEq( const XP_UCHAR* str1, const XP_UCHAR* str2 ) { if ( NULL == str1 ) { str1 = ""; } if ( NULL == str2 ) { str2 = ""; } return 0 == XP_STRCMP(str1, str2); } XP_Bool gi_equal( const CurGameInfo* gi1, const CurGameInfo* gi2 ) { XP_Bool equal = XP_FALSE; int ii; for ( ii = 0; ; ++ii ) { switch ( ii ) { case 0: equal = gi1->gameID == gi2->gameID; break; case 1: equal = gi1->gameSeconds == gi2->gameSeconds; break; case 2: equal = gi1->nPlayers == gi2->nPlayers; break; case 3: equal = gi1->boardSize == gi2->boardSize; break; case 4: equal = gi1->traySize == gi2->traySize; break; case 5: equal = gi1->bingoMin == gi2->bingoMin; break; case 6: equal = gi1->forceChannel == gi2->forceChannel; break; case 7: equal = gi1->serverRole == gi2->serverRole; break; case 8: equal = gi1->hintsNotAllowed == gi2->hintsNotAllowed; break; case 9: equal = gi1->timerEnabled == gi2->timerEnabled; break; case 10: equal = gi1->allowPickTiles == gi2->allowPickTiles; break; case 11: equal = gi1->allowHintRect == gi2->allowHintRect; break; case 12: equal = gi1->inDuplicateMode == gi2->inDuplicateMode; break; case 13: equal = gi1->phoniesAction == gi2->phoniesAction; break; case 14: equal = gi1->confirmBTConnect == gi2->confirmBTConnect; break; case 15: equal = strEq( gi1->dictName, gi2->dictName ); break; case 16: equal = strEq( gi1->isoCodeStr, gi2->isoCodeStr ); break; case 17: for ( int jj = 0; equal && jj < gi1->nPlayers; ++jj ) { const LocalPlayer* lp1 = &gi1->players[jj]; const LocalPlayer* lp2 = &gi2->players[jj]; equal = strEq( lp1->name, lp2->name ) && strEq( lp1->password, lp2->password ) && strEq( lp1->dictName, lp2->dictName ) && lp1->secondsUsed == lp2->secondsUsed && lp1->isLocal == lp2->isLocal && lp1->robotIQ == lp2->robotIQ ; } break; default: goto done; break; } if ( !equal ) { break; } } done: if ( !equal ) { XP_LOGFF( "exited when ii = %d", ii ); LOGGI( gi1, "gi1" ); LOGGI( gi2, "gi2" ); } return equal; } #endif void gi_setNPlayers( CurGameInfo* gi, XWEnv xwe, XW_UtilCtxt* util, XP_U16 nTotal, XP_U16 nHere ) { LOGGI( gi, "before" ); XP_ASSERT( nTotal <= MAX_NUM_PLAYERS ); XP_ASSERT( nHere < nTotal ); gi->nPlayers = nTotal; XP_U16 curLocal = 0; for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { if ( gi->players[ii].isLocal ) { ++curLocal; } } if ( nHere != curLocal ) { /* This will happen when a device has more than one player. Not sure I handle that correctly, but don't assert for now. */ XP_LOGFF( "nHere: %d; curLocal: %d; a problem?", nHere, curLocal ); for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { if ( !gi->players[ii].isLocal ) { gi->players[ii].isLocal = XP_TRUE; XP_LOGFF( "making player #%d local when wasn't before", ii ); ++curLocal; XP_ASSERT( curLocal <= nHere ); if ( curLocal == nHere ) { break; } } } } for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { LocalPlayer* lp = &gi->players[ii]; if ( !lp->name || !lp->name[0] ) { XP_UCHAR name[32]; XP_U16 len = VSIZE(name); dutil_getUsername( util_getDevUtilCtxt( util, xwe ), xwe, ii, LP_IS_LOCAL(lp), LP_IS_ROBOT(lp), name, &len ); replaceStringIfDifferent( util->mpool, &lp->name, name ); } } LOGGI( gi, "after" ); } XP_U16 gi_countLocalPlayers( const CurGameInfo* gi, XP_Bool humanOnly ) { XP_U16 count = 0; XP_U16 nPlayers = gi->nPlayers; const LocalPlayer* lp = gi->players; while ( nPlayers-- ) { if ( lp->isLocal ) { if ( humanOnly && LP_IS_ROBOT(lp) ) { // skip } else { ++count; } } ++lp; } return count; } /* gi_countLocalPlayers */ void gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ) { LocalPlayer* pl; XP_U16 ii; XP_UCHAR* str; XP_U16 strVersion = stream_getVersion( stream ); XP_U16 nColsNBits; XP_ASSERT( 0 < strVersion ); #ifdef STREAM_VERS_BIGBOARD nColsNBits = STREAM_VERS_BIGBOARD > strVersion ? NUMCOLS_NBITS_4 : NUMCOLS_NBITS_5; #else nColsNBits = NUMCOLS_NBITS_4; #endif str = stringFromStream( mpool, stream ); replaceStringIfDifferent( mpool, &gi->dictName, str ); XP_FREEP( mpool, &str ); gi->nPlayers = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); gi->boardSize = (XP_U8)stream_getBits( stream, nColsNBits ); if ( STREAM_VERS_NINETILES <= strVersion ) { gi->traySize = (XP_U8)stream_getBits( stream, NTILES_NBITS_9 ); gi->bingoMin = (XP_U8)stream_getBits( stream, NTILES_NBITS_9 ); } else { gi->traySize = gi->bingoMin = 7; } gi->serverRole = (DeviceRole)stream_getBits( stream, 2 ); /* XP_LOGF( "%s: read serverRole of %d", __func__, gi->serverRole ); */ gi->hintsNotAllowed = stream_getBits( stream, 1 ); if ( strVersion < STREAM_VERS_ROBOTIQ ) { (void)stream_getBits( stream, 2 ); } gi->phoniesAction = (XWPhoniesChoice)stream_getBits( stream, 2 ); gi->timerEnabled = stream_getBits( stream, 1 ); gi->inDuplicateMode = strVersion >= STREAM_VERS_DUPLICATE ? stream_getBits( stream, 1 ) : XP_FALSE; if ( strVersion >= STREAM_VERS_41B4 ) { gi->allowPickTiles = stream_getBits( stream, 1 ); gi->allowHintRect = stream_getBits( stream, 1 ); } else { gi->allowPickTiles = XP_FALSE; gi->allowHintRect = XP_FALSE; } if ( strVersion >= STREAM_VERS_BLUETOOTH ) { gi->confirmBTConnect = stream_getBits( stream, 1 ); } else { gi->confirmBTConnect = XP_TRUE; /* safe given all the 650s out there. */ } if ( STREAM_VERS_MULTIADDR <= strVersion ) { gi->forceChannel = stream_getBits( stream, 2 ); } gi->gameID = strVersion < STREAM_VERS_BLUETOOTH2 ? stream_getU16( stream ) : stream_getU32( stream ); // XP_LOGFF( "read forceChannel: %d for gid %X", gi->forceChannel, gi->gameID ); if ( STREAM_VERS_GI_ISO <= strVersion ) { stringFromStreamHere( stream, gi->isoCodeStr, VSIZE(gi->isoCodeStr) ); } else if ( STREAM_VERS_DICTLANG <= strVersion ) { XP_LangCode dictLang = stream_getU8( stream ); const XP_UCHAR* isoCode = lcToLocale( dictLang ); XP_ASSERT( !!isoCode ); XP_STRNCPY( gi->isoCodeStr, isoCode, VSIZE(gi->isoCodeStr) ); XP_LOGFF( "upgrading; faked isoCode: %s", gi->isoCodeStr ); } if ( gi->timerEnabled || strVersion >= STREAM_VERS_GAMESECONDS ) { gi->gameSeconds = stream_getU16( stream ); } for ( pl = gi->players, ii = 0; ii < gi->nPlayers; ++pl, ++ii ) { str = stringFromStream( mpool, stream ); replaceStringIfDifferent( mpool, &pl->name, str ); XP_FREEP( mpool, &str ); str = stringFromStream( mpool, stream ); replaceStringIfDifferent( mpool, &pl->password, str ); XP_FREEP( mpool, &str ); if ( strVersion >= STREAM_VERS_PLAYERDICTS ) { str = stringFromStream( mpool, stream ); replaceStringIfDifferent( mpool, &pl->dictName, str ); XP_FREEP( mpool, &str ); } pl->secondsUsed = stream_getU16( stream ); pl->robotIQ = ( strVersion < STREAM_VERS_ROBOTIQ ) ? (XP_U8)stream_getBits( stream, 1 ) : stream_getU8( stream ); pl->isLocal = stream_getBits( stream, 1 ); } } /* gi_readFromStream */ void gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi ) { XP_U16 nColsNBits; #ifdef STREAM_VERS_BIGBOARD XP_U16 strVersion = stream_getVersion( stream ); /* XP_LOGF( "%s: strVersion = 0x%x", __func__, strVersion ); */ XP_ASSERT( STREAM_SAVE_PREVWORDS <= strVersion ); nColsNBits = STREAM_VERS_BIGBOARD > strVersion ? NUMCOLS_NBITS_4 : NUMCOLS_NBITS_5; #else nColsNBits = NUMCOLS_NBITS_4; #endif stringToStream( stream, gi->dictName ); stream_putBits( stream, NPLAYERS_NBITS, gi->nPlayers ); stream_putBits( stream, nColsNBits, gi->boardSize ); if ( STREAM_VERS_NINETILES <= strVersion ) { XP_ASSERT( 0 < gi->traySize ); stream_putBits( stream, NTILES_NBITS_9, gi->traySize ); stream_putBits( stream, NTILES_NBITS_9, gi->bingoMin ); } else { XP_LOGFF( "strVersion: %d so not writing traySize", strVersion ); } stream_putBits( stream, 2, gi->serverRole ); stream_putBits( stream, 1, gi->hintsNotAllowed ); stream_putBits( stream, 2, gi->phoniesAction ); stream_putBits( stream, 1, gi->timerEnabled ); stream_putBits( stream, 1, gi->inDuplicateMode ); stream_putBits( stream, 1, gi->allowPickTiles ); stream_putBits( stream, 1, gi->allowHintRect ); stream_putBits( stream, 1, gi->confirmBTConnect ); stream_putBits( stream, 2, gi->forceChannel ); // XP_LOGFF( "wrote forceChannel: %d for gid %X", gi->forceChannel, gi->gameID ); if ( 0 ) { #ifdef STREAM_VERS_BIGBOARD } else if ( STREAM_VERS_BIGBOARD <= strVersion ) { stream_putU32( stream, gi->gameID ); #endif } else { stream_putU16( stream, gi->gameID ); } if ( STREAM_VERS_GI_ISO <= strVersion ) { stringToStream( stream, gi->isoCodeStr ); } else { XP_LangCode code; if ( haveLocaleToLc( gi->isoCodeStr, &code ) ) { stream_putU8( stream, code ); } else { XP_ASSERT( 0 ); } } stream_putU16( stream, gi->gameSeconds ); int ii; const LocalPlayer* pl; for ( pl = gi->players, ii = 0; ii < gi->nPlayers; ++pl, ++ii ) { stringToStream( stream, pl->name ); stringToStream( stream, pl->password ); stringToStream( stream, pl->dictName ); stream_putU16( stream, pl->secondsUsed ); stream_putU8( stream, pl->robotIQ ); stream_putBits( stream, 1, pl->isLocal ); } } /* gi_writeToStream */ #ifdef XWFEATURE_CHANGEDICT static void gi_setDict( MPFORMAL CurGameInfo* gi, const DictionaryCtxt* dict ) { XP_U16 ii; const XP_UCHAR* name = dict_getName( dict ); replaceStringIfDifferent( mpool, &gi->dictName, name ); for ( ii = 0; ii < gi->nPlayers; ++ii ) { const LocalPlayer* pl = &gi->players[ii]; XP_FREEP( mpool, &pl->dictName ); } } #endif XP_Bool player_hasPasswd( const LocalPlayer* player ) { XP_UCHAR* password = player->password; /* XP_ASSERT( player->isLocal ); */ return !!password && *password != '\0'; } /* player_hasPasswd */ XP_Bool player_passwordMatches( const LocalPlayer* player, const XP_UCHAR* buf ) { XP_ASSERT( player->isLocal ); return 0 == XP_STRCMP( player->password, (XP_UCHAR*)buf ); } /* player_passwordMatches */ XP_U16 player_timePenalty( CurGameInfo* gi, XP_U16 playerNum ) { XP_S16 seconds = (gi->gameSeconds / gi->nPlayers); LocalPlayer* player = gi->players + playerNum; XP_U16 result = 0; seconds -= player->secondsUsed; if ( seconds < 0 ) { seconds = -seconds; seconds += 59; result = (seconds/60) * 10; } return result; } /* player_timePenalty */ #ifdef DEBUG void game_logGI( const CurGameInfo* gi, const char* msg, const char* func, int line ) { XP_LOGFF( "msg: %s from %s() line %d; addr: %p", msg, func, line, gi ); if ( !!gi ) { XP_LOGF( " nPlayers: %d", gi->nPlayers ); for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) { const LocalPlayer* lp = &gi->players[ii]; XP_LOGF( " player[%d]: local: %d; robotIQ: %d; name: %s; dict: %s; pwd: %s", ii, lp->isLocal, lp->robotIQ, lp->name, lp->dictName, lp->password ); } XP_LOGF( " forceChannel: %d", gi->forceChannel ); XP_LOGF( " serverRole: %d", gi->serverRole ); XP_LOGF( " gameID: %d", gi->gameID ); XP_LOGF( " dictName: %s", gi->dictName ); XP_LOGF( " isoCode: %s", gi->isoCodeStr ); } } #endif #ifdef CPLUS } #endif