mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-09 05:24:44 +01:00
1181e908dc
When rematching, some users have a convention that e.g. lowest scoring player in the "parent" game goes first. So allow that, providing the choice on each rematch until a default has been chosen. Support changing that default in a new prefs setting. The place I chose to enforce the order was on the host as invitees are registering and being assigned slots. But by then there's no longer any connection to the game that was rematched, e.g. to use its scores. So during the rematched game creation process I create and store with the new game the necessary ordering information. For the 3-and-4 device case, it was also necessary to tweak the information about other guests that the host sends guests (added during earlier work on rematching.)
1080 lines
35 KiB
C
1080 lines
35 KiB
C
/* -*- 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 );
|
|
}
|
|
|
|
#ifdef XWFEATURE_RELAY
|
|
static void
|
|
onRoleChanged( XWEnv xwe, void* closure, XP_Bool amNowGuest )
|
|
{
|
|
XP_ASSERT( amNowGuest );
|
|
XWGame* game = (XWGame*)closure;
|
|
server_onRoleChanged( game->server, xwe, amNowGuest );
|
|
}
|
|
#endif
|
|
|
|
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, procs,
|
|
#ifdef XWFEATURE_RELAY
|
|
nPlayersHere, nPlayersTotal,
|
|
onRoleChanged, game,
|
|
#endif
|
|
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, RematchOrder ro )
|
|
{
|
|
XP_Bool success = XP_FALSE;
|
|
XP_LOGFF( "(newName=%s, ro=%s)", newName, RO2Str(ro) );
|
|
|
|
RematchInfo* rip;
|
|
if ( server_getRematchInfo( oldGame->server, newUtil,
|
|
makeGameID( newUtil ), ro, &rip ) ) {
|
|
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 ) ) {
|
|
if ( !!newGame->comms ) {
|
|
server_setRematchOrder( newGame->server, rip );
|
|
|
|
const CurGameInfo* newGI = newUtil->gameInfo;
|
|
for ( int ii = 0; ; ++ii ) {
|
|
CommsAddrRec guestAddr;
|
|
XP_U16 nPlayersH;
|
|
if ( !server_ri_getAddr( rip, ii, &guestAddr, &nPlayersH ) ) {
|
|
break;
|
|
}
|
|
XP_ASSERT( !comms_addrsAreSame( newGame->comms, &guestAddr,
|
|
&selfAddr ) );
|
|
|
|
NetLaunchInfo nli;
|
|
nli_init( &nli, newGI, selfAddrP, nPlayersH, ii + 1 );
|
|
if ( !!newName ) {
|
|
nli_setGameName( &nli, newName );
|
|
}
|
|
LOGNLI( &nli );
|
|
comms_invite( newGame->comms, xwe, &nli, &guestAddr, XP_TRUE );
|
|
}
|
|
}
|
|
success = XP_TRUE;
|
|
}
|
|
server_disposeRematchInfo( oldGame->server, &rip );
|
|
}
|
|
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,
|
|
#ifdef XWFEATURE_RELAY
|
|
onRoleChanged, game,
|
|
#endif
|
|
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_getIsHost(game->comms) == server_getIsHost(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, NULL );
|
|
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_getIsHost( const XWGame* game )
|
|
{
|
|
XP_Bool result = comms_getIsHost( 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; gameID: %X", msg, func, line,
|
|
!!gi ? gi->gameID:0 );
|
|
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( " dictName: %s", gi->dictName );
|
|
XP_LOGF( " isoCode: %s", gi->isoCodeStr );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CPLUS
|
|
}
|
|
#endif
|