enable rematch for 3- and 4-device games

Because only the host/inviter knows the addresses of all the devices
in a game it's hard for guests to rematch (unless it's a 2-device
game, as they know the host's address.) So now, as part of telling
guests the game is ready to play, include the addresses of other
guests. It's usually only 9 bytes per device, and only happens when
more than two devices are in a game.
This commit is contained in:
Eric House 2023-02-16 21:36:46 -08:00
parent dcc9cd553c
commit 9ca291cf0d
17 changed files with 355 additions and 202 deletions

View file

@ -857,7 +857,7 @@ public class BoardDelegate extends DelegateBase
}
}
enable = m_gameOver && rematchSupported( false );
enable = null != m_gsi && m_gsi.canRematch;
Utils.setItemVisible( menu, R.id.board_menu_rematch, enable );
enable = m_gameOver && !inArchiveGroup();
@ -2879,53 +2879,6 @@ public class BoardDelegate extends DelegateBase
}
}
// For now, supported if standalone or either BT or SMS used for transport
private boolean rematchSupported( boolean showMulti )
{
return null != m_summary
&& rematchSupported( showMulti ? m_activity : null,
m_summary );
}
public static boolean rematchSupported( Context context, long rowID )
{
GameSummary summary = GameUtils.getSummary( context, rowID, 1 );
return null != summary && rematchSupported( null, summary );
}
private static boolean rematchSupported( Context context,
GameSummary summary )
{
return rematchSupported( context, false, summary );
}
private static boolean rematchSupported( Context context, boolean supported,
GameSummary summary )
{
// standalone games are easy to rematch
supported = summary.serverRole == DeviceRole.SERVER_STANDALONE;
if ( !supported ) {
if ( 2 == summary.nPlayers ) {
if ( !summary.anyMissing() ) {
CommsConnTypeSet connTypes = summary.conTypes;
supported = connTypes.contains( CommsConnType.COMMS_CONN_MQTT )
|| connTypes.contains( CommsConnType.COMMS_CONN_BT )
|| connTypes.contains( CommsConnType.COMMS_CONN_SMS )
|| connTypes.contains( CommsConnType.COMMS_CONN_P2P );
}
} else if ( null != context ) {
// show the button if people haven't dismissed the hint yet
supported = ! XWPrefs
.getPrefsBoolean( context,
R.string.key_na_rematch_two_only,
false );
}
}
return supported;
}
private void doRematchIf( boolean deleteAfter )
{
doRematchIf( GROUPID_UNSPEC, deleteAfter );
@ -2942,27 +2895,12 @@ public class BoardDelegate extends DelegateBase
GameSummary summary, CurGameInfo gi,
GamePtr jniGamePtr, boolean deleteAfter )
{
boolean doIt = true;
if ( DeviceRole.SERVER_STANDALONE == gi.serverRole ) {
// nothing to do??
} else if ( 2 != gi.nPlayers ) {
Assert.assertNotNull( dlgt );
if ( null != dlgt ) {
dlgt.makeNotAgainBuilder( R.string.key_na_rematch_two_only,
R.string.not_again_rematch_two_only )
.show();
}
doIt = false;
}
if ( doIt ) {
String newName = summary.getRematchName( activity );
Intent intent = GamesListDelegate
.makeRematchIntent( activity, rowid, groupID, gi,
summary.conTypes, newName, deleteAfter );
if ( null != intent ) {
activity.startActivity( intent );
}
String newName = summary.getRematchName( activity );
Intent intent = GamesListDelegate
.makeRematchIntent( activity, rowid, groupID, gi,
summary.conTypes, newName, deleteAfter );
if ( null != intent ) {
activity.startActivity( intent );
}
}

View file

@ -589,8 +589,6 @@ public class GameUtils {
if ( null != gamePtrNew ) {
rowid = saveNewGame1( context, gamePtrNew,
groupID, gameName );
} else {
Assert.failDbg();
}
}
}
@ -599,7 +597,6 @@ public class GameUtils {
}
Log.d( TAG, "makeRematch() => %d", rowid );
Assert.assertTrueNR( DBUtils.ROWID_NOTFOUND != rowid );
return rowid;
}

View file

@ -1750,14 +1750,6 @@ public class GamesListDelegate extends ListDelegateBase
.contains( XWPrefs.getDefaultNewGameGroup( m_activity ) );
Utils.setItemVisible( menu, R.id.games_group_default, enable );
// Rematch supported if there's one game selected
enable = 1 == nGamesSelected;
if ( enable ) {
enable = BoardDelegate.rematchSupported( m_activity,
getSelRowIDs()[0] );
}
Utils.setItemVisible( menu, R.id.games_game_rematch, enable );
// Move up/down enabled for groups if not the top-most or bottommost
// selected
enable = 1 == nGroupsSelected;
@ -1991,8 +1983,6 @@ public class GamesListDelegate extends ListDelegateBase
if ( null != gameItem ) {
long rowID = gameItem.getRowID();
enable = BoardDelegate.rematchSupported( m_activity, rowID );
Utils.setItemVisible( menu, R.id.games_game_rematch, enable );
// Deal with possibility summary's temporarily null....
GameSummary summary = gameItem.getSummary();

View file

@ -129,11 +129,14 @@ public class JNIThread extends Thread implements AutoCloseable {
public boolean canTrade;
public boolean canPause;
public boolean canUnpause;
public boolean canRematch;
@Override
public GameStateInfo clone() {
GameStateInfo obj = null;
GameStateInfo obj;
try {
obj = (GameStateInfo)super.clone();
} catch ( CloneNotSupportedException cnse ) {
obj = null;
}
return obj;
}

View file

@ -346,7 +346,10 @@ public class XwJNI {
CommonPrefs cp, String gameName )
{
GamePtr gamePtrNew = initGameJNI( 0 );
game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName );
if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName ) ) {
gamePtrNew.release();
gamePtrNew = null;
}
return gamePtrNew;
}
@ -384,10 +387,10 @@ public class XwJNI {
CommonPrefs cp,
TransportProcs procs );
private static native void game_makeRematch( GamePtr gamePtr,
GamePtr gamePtrNew,
UtilCtxt util, CommonPrefs cp,
String gameName );
private static native boolean game_makeRematch( GamePtr gamePtr,
GamePtr gamePtrNew,
UtilCtxt util, CommonPrefs cp,
String gameName );
private static native boolean game_makeFromInvite( GamePtr gamePtr, NetLaunchInfo nli,
UtilCtxt util,

View file

@ -1495,11 +1495,12 @@ initGameGlobals( JNIEnv* env, JNIState* state, jobject jutil, jobject jprocs )
globals->jniutil = state->globalJNI->jniutil;
}
JNIEXPORT void JNICALL
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch
( JNIEnv* env, jclass C, GamePtrType gamePtr, GamePtrType gamePtrNew,
jobject jutil, jobject jcp, jstring jGameName )
{
jboolean success = false;
XWJNI_START_GLOBALS(gamePtr);
JNIState* oldState = state; /* state about to go out-of-scope */
@ -1511,12 +1512,13 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch
loadCommonPrefs( env, &cp, jcp );
const char* gameName = (*env)->GetStringUTFChars( env, jGameName, NULL );
game_makeRematch( &oldState->game, env, globals->util, &cp,
&state->game, gameName, XP_FALSE );
success = game_makeRematch( &oldState->game, env, globals->util, &cp,
(TransportProcs*)NULL, &state->game, gameName );
(*env)->ReleaseStringUTFChars( env, jGameName, gameName );
XWJNI_END(); /* matches second XWJNI_START_GLOBALS! */
XWJNI_END();
return success;
}
JNIEXPORT jboolean JNICALL
@ -2540,6 +2542,7 @@ static const SetInfo gsi_bools[] = {
ARR_MEMBER( GameStateInfo, canTrade ),
ARR_MEMBER( GameStateInfo, canPause ),
ARR_MEMBER( GameStateInfo, canUnpause ),
ARR_MEMBER( GameStateInfo, canRematch ),
};
JNIEXPORT void JNICALL

View file

@ -329,8 +329,8 @@ static void assertQueueOk( const CommsCtxt* comms );
static const char* relayCmdToStr( XWRELAY_Cmd cmd );
# endif
static void printQueue( const CommsCtxt* comms );
static void logAddr( const CommsCtxt* comms, const CommsAddrRec* addr,
const char* caller );
static void logAddrComms( const CommsCtxt* comms, const CommsAddrRec* addr,
const char* caller );
#else
# define ASSERT_ADDR_OK(addr)
# define assertQueueOk(comms)
@ -556,13 +556,13 @@ comms_make( MPFORMAL XWEnv xwe, XW_UtilCtxt* util, XP_Bool isServer,
if ( !!selfAddr ) {
ASSERT_ADDR_OK(selfAddr);
logAddr( comms, &comms->selfAddr, "before selfAddr" );
logAddrComms( comms, &comms->selfAddr, "before selfAddr" );
comms->selfAddr = *selfAddr;
logAddr( comms, &comms->selfAddr, "after selfAddr" );
logAddrComms( comms, &comms->selfAddr, "after selfAddr" );
}
if ( !!hostAddr ) {
XP_ASSERT( !isServer );
logAddr( comms, hostAddr, __func__ );
logAddrComms( comms, hostAddr, __func__ );
XP_PlayerAddr channelNo = comms_getChannelSeed( comms );
#ifdef DEBUG
AddressRecord* rec =
@ -772,8 +772,9 @@ void
addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream )
{
XP_U8 tmp = stream_getU8( stream );
if ( (STREAM_VERS_MULTIADDR > stream_getVersion( stream ))
&& (COMMS_CONN_NONE != tmp) ) {
XP_U16 version = stream_getVersion( stream );
XP_ASSERT( 0 < version );
if ( STREAM_VERS_MULTIADDR > version && (COMMS_CONN_NONE != tmp) ) {
tmp = 1 << (tmp - 1);
}
addrP->_conTypes = tmp;
@ -879,7 +880,7 @@ comms_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream,
XP_USE( nPlayersTotal );
#endif
XP_MEMCPY( &comms->selfAddr, &selfAddr, sizeof(comms->selfAddr) );
logAddr( comms, &selfAddr, __func__ );
logAddrComms( comms, &selfAddr, __func__ );
comms->flags = flags;
comms->connID = stream_getU32( stream );
@ -914,7 +915,7 @@ comms_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream,
AddressRecord* rec = (AddressRecord*)XP_CALLOC( mpool, sizeof(*rec));
addrFromStream( &rec->addr, stream );
logAddr( comms, &rec->addr, __func__ );
logAddrComms( comms, &rec->addr, __func__ );
if ( STREAM_VERS_SMALLCOMMS <= version ) {
rec->nextMsgID = stream_getU32VL( stream );
@ -1155,8 +1156,7 @@ addrToStreamOne( XWStreamCtxt* stream, CommsConnType typ,
void
addrToStream( XWStreamCtxt* stream, const CommsAddrRec* addrP )
{
stream_setVersion( stream, CUR_STREAM_VERS );
XP_ASSERT( CUR_STREAM_VERS >= STREAM_VERS_NORELAY );
XP_ASSERT( 0 < stream_getVersion(stream) ); /* must be set already */
XP_U16 conTypes = addrP->_conTypes;
types_rmType( &conTypes, COMMS_CONN_RELAY );
stream_putU8( stream, conTypes );
@ -1209,7 +1209,7 @@ comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, XP_U16 saveToken )
stream_setVersion( stream, CUR_STREAM_VERS );
stream_putU8( stream, comms->flags );
logAddr( comms, &comms->selfAddr, __func__ );
logAddrComms( comms, &comms->selfAddr, __func__ );
addrToStream( stream, &comms->selfAddr );
stream_putBits( stream, 4, comms->rr.nPlayersHere );
stream_putBits( stream, 4, comms->rr.nPlayersTotal );
@ -1239,7 +1239,7 @@ comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, XP_U16 saveToken )
for ( rec = comms->recs; !!rec; rec = rec->next ) {
const CommsAddrRec* addr = &rec->addr;
logAddr( comms, addr, __func__ );
logAddrComms( comms, addr, __func__ );
addrToStream( stream, addr );
stream_putU32VL( stream, rec->nextMsgID );
@ -1364,18 +1364,36 @@ comms_addMQTTDevID( CommsCtxt* comms, XP_PlayerAddr channelNo,
}
void
comms_getAddrs( const CommsCtxt* comms, CommsAddrRec addr[],
XP_U16* nRecs )
comms_getAddrs( const CommsCtxt* comms, CommsAddrRec addrs[],
XP_U16* nAddrs )
{
AddressRecord* recs;
XP_U16 count;
for ( count = 0, recs = comms->recs;
count < *nRecs && !!recs;
++count, recs = recs->next ) {
XP_MEMCPY( &addr[count], &recs->addr, sizeof(addr[count]) );
logAddr( comms, &addr[count], __func__ );
XP_U16 count = 0;
for ( AddressRecord* recs = comms->recs; !!recs; recs = recs->next ) {
if ( count < *nAddrs ) {
XP_MEMCPY( &addrs[count], &recs->addr, sizeof(addrs[count]) );
logAddrComms( comms, &addrs[count], __func__ );
}
++count;
}
*nRecs = count;
*nAddrs = count;
}
void
comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
CommsAddrRec* addr )
{
XP_U16 masked = channelNo & CHANNEL_MASK;
XP_Bool found = XP_FALSE;
for ( const AddressRecord* rec = comms->recs;
!found && !!rec; rec = rec->next ) {
found = (rec->channelNo & CHANNEL_MASK) == masked;
if ( found ) {
XP_LOGFF( "writing addr for channel %X", channelNo );
*addr = rec->addr;
logAddrComms( comms, addr, __func__ );
}
}
XP_ASSERT( found );
}
XP_U16
@ -1683,7 +1701,7 @@ comms_invite( CommsCtxt* comms, XWEnv xwe, const NetLaunchInfo* nli,
followed by opening the game, which results in comms_resendAll()
getting called leading to a second send immediately after this. So
let Android drop it. Linux, though, needs it for now. */
if ( sendNow ) {
if ( sendNow && !!comms->procs.sendInvt ) {
sendMsg( comms, xwe, elem, COMMS_CONN_NONE );
}
}
@ -2101,7 +2119,7 @@ sendMsg( const CommsCtxt* comms, XWEnv xwe, MsgQueueElem* elem,
} else {
XP_ASSERT( !!comms->procs.sendMsg );
XP_U32 gameid = gameID( comms );
logAddr( comms, &addr, __func__ );
logAddrComms( comms, &addr, __func__ );
XP_UCHAR msgNo[16];
formatMsgNo( comms, elem, msgNo, sizeof(msgNo) );
XP_ASSERT( 0 != elem->createdStamp );
@ -2643,7 +2661,15 @@ getRecordFor( const CommsCtxt* comms, const XP_PlayerAddr channelNo )
XP_LOGFF( "comparing rec channel %s with addr channel %s",
cbuf1, cbuf );
if ( (rec->channelNo & ~CHANNEL_MASK) == (channelNo & ~CHANNEL_MASK) ) {
/* Invite case: base on channelNo bits if the rest is 0 */
if ( (0 == (rec->channelNo & ~CHANNEL_MASK)) && (0 == (channelNo & ~CHANNEL_MASK)) ) {
if ( rec->channelNo == channelNo ) {
break;
}
} else if ( (rec->channelNo & ~CHANNEL_MASK) == (channelNo & ~CHANNEL_MASK) ) {
XP_LOGFF( "match based on channels!!!" );
/* This is so wrong for addresses coming from invites. Why works
with GTK? */
break;
}
}
@ -3449,7 +3475,7 @@ rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
XP_LOGFF( "(%s)", cbuf );
listRecs( comms, "entering rememberChannelAddress" );
logAddr( comms, addr, __func__ );
logAddrComms( comms, addr, __func__ );
rec = getRecordFor( comms, channelNo );
if ( !rec ) {
/* not found; add a new entry */
@ -3487,17 +3513,27 @@ rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
} /* rememberChannelAddress */
#ifdef DEBUG
static void
logAddr( const CommsCtxt* comms, const CommsAddrRec* addr,
static void
logAddrComms( const CommsCtxt* comms, const CommsAddrRec* addr,
const char* caller )
{
logAddr( MPPARM(comms->mpool) comms->dutil, addr, caller );
}
void
logAddr( MPFORMAL XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
const char* caller )
{
if ( !!addr ) {
char buf[128];
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(comms->mpool)
dutil_getVTManager(comms->dutil));
snprintf( buf, sizeof(buf), TAGFMT() "called on %p from %s:\n", TAGPRMS,
addr, caller );
stream_catString( stream, buf );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(mpool)
dutil_getVTManager(dutil));
if ( !!caller ) {
snprintf( buf, sizeof(buf), "called on %p from %s:\n",
addr, caller );
stream_catString( stream, buf );
}
CommsConnType typ;
XP_Bool first = XP_TRUE;

View file

@ -214,6 +214,8 @@ XP_S16 comms_resendAll( CommsCtxt* comms, XWEnv xwe, CommsConnType filter,
XP_Bool force );
XP_U16 comms_getChannelSeed( CommsCtxt* comms );
void comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
CommsAddrRec* addr );
#ifdef XWFEATURE_COMMSACK
void comms_ackAny( CommsCtxt* comms, XWEnv xwe );
@ -277,6 +279,8 @@ void comms_setAddrDisabled( CommsCtxt* comms, CommsConnType typ,
XP_Bool send, XP_Bool enabled );
XP_Bool comms_getAddrDisabled( const CommsCtxt* comms, CommsConnType typ,
XP_Bool send );
void logAddr( MPFORMAL XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
const char* caller );
# else
# define comms_setAddrDisabled( comms, typ, send, enabled )

View file

@ -48,6 +48,7 @@
#define MAX_COLS MAX_ROWS
#define MIN_COLS 11
#define STREAM_VERS_REMATCHADDRS 0x24
#define STREAM_VERS_MSGSTREAMVERS 0x23
#define STREAM_VERS_NORELAY 0x22
#define STREAM_VERS_MSGTIMESTAMP 0x21
@ -98,7 +99,7 @@
#define STREAM_VERS_405 0x01
/* search for FIX_NEXT_VERSION_CHANGE next time this is changed */
#define CUR_STREAM_VERS STREAM_VERS_MSGSTREAMVERS
#define CUR_STREAM_VERS STREAM_VERS_REMATCHADDRS
typedef struct XP_Rect {
XP_S16 left;

View file

@ -165,6 +165,7 @@ game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
#endif
)
{
XP_ASSERT( gi == util->gameInfo ); /* if holds, remove gi param */
#ifndef XWFEATURE_STANDALONE_ONLY
XP_U16 nPlayersHere = 0;
XP_U16 nPlayersTotal = 0;
@ -231,58 +232,44 @@ game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
return success;
} /* game_makeNewGame */
void
XP_Bool
game_makeRematch( const XWGame* oldGame, XWEnv xwe, XW_UtilCtxt* newUtil,
const CommonPrefs* newCp, XWGame* newGame,
const XP_UCHAR* newName, XP_Bool sendNow )
const CommonPrefs* newCp, const TransportProcs* procs,
XWGame* newGame, const XP_UCHAR* newName )
{
LOG_FUNC();
CurGameInfo* newGI = newUtil->gameInfo;
XP_ASSERT( !!newGI );
XP_ASSERT( newGI != oldGame->util->gameInfo );
const CurGameInfo* gi = oldGame->util->gameInfo;
XP_ASSERT( 0 < gi->nPlayers );
gi_copy( MPPARM(newUtil->mpool) newGI, gi );
newGI->gameID = makeGameID( newUtil );
if ( SERVER_ISCLIENT == newGI->serverRole ) {
newGI->serverRole = SERVER_ISSERVER; /* we'll be inviting */
newGI->forceChannel = 0;
}
XP_Bool success = XP_FALSE;
XP_LOGFF( "(newName=%s)", newName );
CommsAddrRec* selfAddrP = NULL;
CommsAddrRec selfAddr;
if ( !!oldGame->comms ) {
comms_getSelfAddr( oldGame->comms, &selfAddr );
selfAddrP = &selfAddr;
}
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;
}
CommsAddrRec hostAddr;
XP_Bool haveRemote = !oldGame->comms
|| comms_getHostAddr( oldGame->comms, &hostAddr );
if ( !haveRemote ) {
XP_U16 nRecs = 1;
comms_getAddrs( oldGame->comms, &hostAddr, &nRecs );
haveRemote = 0 < nRecs;
}
XP_ASSERT( haveRemote );
if ( game_makeNewGame( MPPARM(newUtil->mpool) xwe, newGame, newUtil->gameInfo,
selfAddrP, (CommsAddrRec*)NULL, newUtil,
(DrawCtx*)NULL, newCp, procs ) ) {
if ( haveRemote &&
game_makeNewGame( MPPARM(newUtil->mpool) xwe, newGame, newGI,
selfAddrP, (CommsAddrRec*)NULL, newUtil,
(DrawCtx*)NULL, newCp, (TransportProcs*)NULL ) ) {
LOGGI(newGI, "made game" );
XP_ASSERT( 0 < newGI->nPlayers );
if ( !!newGame->comms ) {
NetLaunchInfo nli;
nli_init( &nli, newGI, selfAddrP, 1, 1 );
if ( !!newName ) {
nli_setGameName( &nli, newName );
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 );
}
LOGNLI( &nli );
comms_invite( newGame->comms, xwe, &nli, &hostAddr, sendNow );
success = XP_TRUE;
}
}
LOG_RETURN_VOID();
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}
#ifdef XWFEATURE_CHANGEDICT
@ -526,6 +513,7 @@ game_getState( const XWGame* game, XWEnv xwe, GameStateInfo* gsi )
gsi->nPendingMessages = !!game->comms ?
comms_countPendingPackets(game->comms) : 0;
gsi->canRematch = server_canRematch( server );
gsi->canPause = server_canPause( server );
gsi->canUnpause = server_canUnpause( server );
}

View file

@ -47,6 +47,7 @@ typedef struct _GameStateInfo {
XP_Bool curTurnSelected;
XP_Bool canHideRack;
XP_Bool canTrade;
XP_Bool canRematch;
XP_Bool canPause; /* duplicate-mode only */
XP_Bool canUnpause; /* duplicate-mode only */
} GameStateInfo;
@ -84,9 +85,9 @@ XP_Bool game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
,XP_U16 gameSeed
#endif
);
void game_makeRematch( const XWGame* game, XWEnv xwe, XW_UtilCtxt* util,
const CommonPrefs* cp, XWGame* newGame,
const XP_UCHAR* newName, XP_Bool sendNow );
XP_Bool game_makeRematch( const XWGame* game, XWEnv xwe, XW_UtilCtxt* util,
const CommonPrefs* cp, const TransportProcs* procs,
XWGame* newGame, const XP_UCHAR* newName );
void game_changeDict( MPFORMAL XWGame* game, XWEnv xwe, CurGameInfo* gi,
DictionaryCtxt* dict );

View file

@ -21,7 +21,7 @@
#ifndef _GAMEINFO_H_
#define _GAMEINFO_H_
#include "server.h"
#include "nlityp.h"
#ifdef CPLUS
extern "C" {

View file

@ -92,6 +92,7 @@ saveState( XW_DUtilCtxt* dutil, XWEnv xwe, KPState* state )
if ( state->dirty ) {
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(dutil->mpool)
dutil_getVTManager(dutil) );
stream_setVersion( stream, CUR_STREAM_VERS );
stream_putU8( stream, CUR_STREAM_VERS );
for ( KnownPlayer* kp = state->players; !!kp; kp = kp->next ) {
stream_putU32( stream, kp->newestMod );

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */
/*
* Copyright 1997 - 2021 by Eric House (xwords@eehouse.org). All rights
* Copyright 1997 - 2023 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -86,12 +86,12 @@ typedef enum {
#define UNKNOWN_DEVICE -1
#define SERVER_DEVICE 0
typedef struct ServerPlayer {
typedef struct _ServerPlayer {
EngineCtxt* engine; /* each needs his own so don't interfere each other */
XP_S8 deviceIndex; /* 0 means local, -1 means unknown */
} ServerPlayer;
typedef struct RemoteAddress {
typedef struct _RemoteAddress {
XP_PlayerAddr channelNo;
#ifdef STREAM_VERS_BIGBOARD
XP_U8 streamVersion;
@ -143,9 +143,15 @@ typedef struct ServerNonvolatiles {
RemoteAddress addresses[MAX_NUM_PLAYERS];
XWStreamCtxt* prevMoveStream; /* save it to print later */
XWStreamCtxt* prevWordsStream;
/* On guests only, stores addresses of other clients for rematch use*/
XP_U16 rematchAddrsLen;
XP_U8* rematchAddrs;
XP_Bool dupTurnsMade[MAX_NUM_PLAYERS];
XP_Bool dupTurnsForced[MAX_NUM_PLAYERS];
XP_Bool dupTurnsSent; /* used on client only */
XP_Bool dupTurnsSent; /* used on guest only */
} ServerNonvolatiles;
struct ServerCtxt {
@ -157,7 +163,7 @@ struct ServerCtxt {
BadWordInfo illegalWordInfo;
XP_U16 lastMoveSource;
ServerPlayer players[MAX_NUM_PLAYERS];
ServerPlayer srvPlyrs[MAX_NUM_PLAYERS];
XP_Bool serverDoing;
#ifdef XWFEATURE_SLOW_ROBOT
XP_Bool robotWaiting;
@ -194,7 +200,7 @@ static void badWordMoveUndoAndTellUser( ServerCtxt* server, XWEnv xwe,
BadWordInfo* bwi );
static XP_Bool tileCountsOk( const ServerCtxt* server );
static void setTurn( ServerCtxt* server, XWEnv xwe, XP_S16 turn );
static XWStreamCtxt* mkServerStream( ServerCtxt* server );
static XWStreamCtxt* mkServerStream( const ServerCtxt* server );
static void fetchTiles( ServerCtxt* server, XWEnv xwe, XP_U16 playerNum,
XP_U16 nToFetch, const TrayTileSet* tradedTiles,
TrayTileSet* resultTiles, XP_Bool forceCanPlay );
@ -232,6 +238,8 @@ static XP_Bool handleIllegalWord( ServerCtxt* server, XWEnv xwe,
static void tellMoveWasLegal( ServerCtxt* server, XWEnv xwe );
static void writeProto( const ServerCtxt* server, XWStreamCtxt* stream,
XW_Proto proto );
static void readGuestAddrs( ServerCtxt* server, XWStreamCtxt* stream );
#endif
#define PICK_NEXT -1
@ -301,7 +309,7 @@ syncPlayers( ServerCtxt* server )
XP_U16 ii;
CurGameInfo* gi = server->vol.gi;
LocalPlayer* lp = gi->players;
ServerPlayer* player = server->players;
ServerPlayer* player = server->srvPlyrs;
for ( ii = 0; ii < gi->nPlayers; ++ii, ++lp, ++player ) {
if ( !lp->isLocal/* && !lp->name */ ) {
++server->nv.pendingRegistrations;
@ -553,7 +561,7 @@ server_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, ModelCtxt* mode
}
for ( ii = 0; ii < nPlayers; ++ii ) {
ServerPlayer* player = &server->players[ii];
ServerPlayer* player = &server->srvPlyrs[ii];
player->deviceIndex = stream_getU8( stream );
@ -578,12 +586,17 @@ server_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, ModelCtxt* mode
server->nv.prevWordsStream = readStreamIf( server, stream );
}
if ( server->vol.gi->serverRole == SERVER_ISCLIENT
&& 2 < nPlayers ) {
readGuestAddrs( server, stream );
}
/* Hack alert: recovering from an apparent bug that leaves the game
thinking it's a client but being in the host-only XWSTATE_BEGIN
state. */
if ( server->nv.gameState == XWSTATE_BEGIN &&
server->vol.gi->serverRole == SERVER_ISCLIENT ) {
XP_LOGFF( "server_makeFromStream(): fixing state" );
XP_LOGFF( "fixing state" );
SETSTATE( server, XWSTATE_NONE );
}
@ -594,7 +607,7 @@ server_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, ModelCtxt* mode
void
server_writeToStream( const ServerCtxt* server, XWStreamCtxt* stream )
{
XP_U16 nPlayers = server->vol.gi->nPlayers;
const XP_U16 nPlayers = server->vol.gi->nPlayers;
putNV( stream, &server->nv, nPlayers );
@ -604,7 +617,7 @@ server_writeToStream( const ServerCtxt* server, XWStreamCtxt* stream )
}
for ( int ii = 0; ii < nPlayers; ++ii ) {
const ServerPlayer* player = &server->players[ii];
const ServerPlayer* player = &server->srvPlyrs[ii];
stream_putU8( stream, player->deviceIndex );
@ -618,6 +631,13 @@ server_writeToStream( const ServerCtxt* server, XWStreamCtxt* stream )
writeStreamIf( stream, server->nv.prevMoveStream );
writeStreamIf( stream, server->nv.prevWordsStream );
if ( server->vol.gi->serverRole == SERVER_ISCLIENT
&& 2 < nPlayers ) {
XP_U16 len = server->nv.rematchAddrsLen;
stream_putU32VL( stream, len );
stream_putBytes( stream, server->nv.rematchAddrs, len );
}
} /* server_writeToStream */
void
@ -638,14 +658,13 @@ server_onRoleChanged( ServerCtxt* server, XWEnv xwe, XP_Bool amNowGuest )
static void
cleanupServer( ServerCtxt* server )
{
XP_U16 ii;
for ( ii = 0; ii < VSIZE(server->players); ++ii ){
ServerPlayer* player = &server->players[ii];
for ( XP_U16 ii = 0; ii < VSIZE(server->srvPlyrs); ++ii ){
ServerPlayer* player = &server->srvPlyrs[ii];
if ( player->engine != NULL ) {
engine_destroy( player->engine );
}
}
XP_MEMSET( server->players, 0, sizeof(server->players) );
XP_MEMSET( server->srvPlyrs, 0, sizeof(server->srvPlyrs) );
if ( server->pool != NULL ) {
pool_destroy( server->pool );
@ -659,6 +678,8 @@ cleanupServer( ServerCtxt* server )
stream_destroy( server->nv.prevWordsStream );
}
XP_FREEP( server->mpool, &server->nv.rematchAddrs );
XP_MEMSET( &server->nv, 0, sizeof(server->nv) );
} /* cleanupServer */
@ -778,6 +799,66 @@ readMQTTDevID( ServerCtxt* server, XWStreamCtxt* stream )
}
}
static void
addGuestAddrsIf( const ServerCtxt* server, XP_U16 sendee, XWStreamCtxt* stream )
{
XP_LOGFF("(sendee: %d)", sendee );
if ( amServer( server )
/* Not needed for two-device games */
&& 2 < server->nv.nDevices
/* no two-player devices? */
&& server->nv.nDevices == server->vol.gi->nPlayers
&& STREAM_VERS_REMATCHADDRS <= stream_getVersion(stream) ) {
XWStreamCtxt* tmpStream = mkServerStream( server );
stream_setVersion( tmpStream, stream_getVersion( stream ) );
for ( XP_U16 devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) {
if ( devIndex == sendee ) {
continue;
}
XP_PlayerAddr channelNo
= server->nv.addresses[devIndex].channelNo;
CommsAddrRec addr;
comms_getChannelAddr( server->vol.comms, channelNo, &addr );
addrToStream( tmpStream, &addr );
}
XP_U16 len = stream_getSize( tmpStream );
stream_putU32VL( stream, len );
stream_putBytes( stream, stream_getPtr(tmpStream), len );
stream_destroy( tmpStream );
}
}
static void
readGuestAddrs( ServerCtxt* server, XWStreamCtxt* stream )
{
XP_U16 version = stream_getVersion( stream );
XP_LOGFF( "version: %X", version );
if ( STREAM_VERS_REMATCHADDRS <= version && 0 < stream_getSize(stream) ) {
XP_U16 len = server->nv.rematchAddrsLen = stream_getU32VL( stream );
XP_LOGFF( "rematchAddrsLen: %d", server->nv.rematchAddrsLen );
if ( 0 < len ) {
XP_ASSERT( !server->nv.rematchAddrs );
server->nv.rematchAddrs = XP_MALLOC( server->mpool, len );
stream_getBytes( stream, server->nv.rematchAddrs, len );
XP_LOGFF( "loaded %d bytes of rematchAddrs", len );
}
#ifdef DEBUG
XWStreamCtxt* tmpStream = mkServerStream( server );
stream_setVersion( tmpStream, stream_getVersion( stream ) );
stream_putBytes( tmpStream, server->nv.rematchAddrs, server->nv.rematchAddrsLen );
while ( 0 < stream_getSize(tmpStream) ) {
CommsAddrRec addr = {0};
addrFromStream( &addr, tmpStream );
XP_LOGFF( "got an address" );
logAddr( MPPARM(server->mpool) server->vol.dutil, &addr, __func__ );
}
stream_destroy( tmpStream );
#endif
}
LOG_RETURN_VOID();
}
XP_Bool
server_initClientConnection( ServerCtxt* server, XWEnv xwe )
{
@ -1401,11 +1482,11 @@ robotTradeTiles( ServerCtxt* server, MoveInfo* newMove )
#endif
static XWStreamCtxt*
mkServerStream( ServerCtxt* server )
mkServerStream( const ServerCtxt* server )
{
XWStreamCtxt* stream;
stream = mem_stream_make_raw( MPPARM(server->mpool)
dutil_getVTManager(server->vol.dutil) );
XWStreamCtxt* stream =
mem_stream_make_raw( MPPARM(server->mpool)
dutil_getVTManager(server->vol.dutil) );
XP_ASSERT( !!stream );
return stream;
} /* mkServerStream */
@ -1847,7 +1928,7 @@ findFirstPending( ServerCtxt* server, ServerPlayer** playerP )
XP_LOGFF( "no slot found for player; duplicate packet?" );
lp = NULL;
} else {
*playerP = server->players + nPlayers;
*playerP = server->srvPlyrs + nPlayers;
}
return lp;
} /* findFirstPending */
@ -2042,6 +2123,7 @@ client_readInitialMessage( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream )
}
readMQTTDevID( server, stream );
readGuestAddrs( server, stream );
syncPlayers( server );
@ -2078,7 +2160,7 @@ makeSendableGICopy( ServerCtxt* server, CurGameInfo* giCopy,
for ( clientPl = giCopy->players, ii = 0;
ii < nPlayers; ++clientPl, ++ii ) {
/* adjust isLocal to client's perspective */
clientPl->isLocal = server->players[ii].deviceIndex == deviceIndex;
clientPl->isLocal = server->srvPlyrs[ii].deviceIndex == deviceIndex;
}
giCopy->forceChannel = deviceIndex;
@ -2139,6 +2221,7 @@ sendInitialMessage( ServerCtxt* server, XWEnv xwe )
}
addMQTTDevIDIf( server, xwe, stream );
addGuestAddrsIf( server, deviceIndex, stream );
stream_destroy( stream );
}
@ -2288,7 +2371,7 @@ server_getEngineFor( ServerCtxt* server, XP_U16 playerNum )
const CurGameInfo* gi = server->vol.gi;
XP_ASSERT( playerNum < gi->nPlayers );
ServerPlayer* player = &server->players[playerNum];
ServerPlayer* player = &server->srvPlyrs[playerNum];
EngineCtxt* engine = player->engine;
if ( !engine &&
(inDuplicateMode(server) || gi->players[playerNum].isLocal) ) {
@ -2303,7 +2386,7 @@ server_getEngineFor( ServerCtxt* server, XP_U16 playerNum )
void
server_resetEngine( ServerCtxt* server, XP_U16 playerNum )
{
ServerPlayer* player = &server->players[playerNum];
ServerPlayer* player = &server->srvPlyrs[playerNum];
if ( !!player->engine ) {
XP_ASSERT( player->deviceIndex == 0 || inDuplicateMode(server) );
engine_reset( player->engine );
@ -3816,6 +3899,7 @@ server_getGameIsOver( const ServerCtxt* server )
return server->nv.gameState == XWSTATE_GAMEOVER;
} /* server_getGameIsOver */
/* This is completely wrong */
XP_Bool
server_getGameIsConnected( const ServerCtxt* server )
{
@ -3849,7 +3933,7 @@ server_getMissingPlayers( const ServerCtxt* server )
case SERVER_ISSERVER:
if ( 0 < server->nv.pendingRegistrations ) {
XP_U16 nPlayers = server->vol.gi->nPlayers;
const ServerPlayer* players = server->players;
const ServerPlayer* players = server->srvPlyrs;
for ( ii = 0; ii < nPlayers; ++ii ) {
if ( players->deviceIndex == UNKNOWN_DEVICE ) {
result |= 1 << ii;
@ -3870,7 +3954,7 @@ server_getOpenChannel( const ServerCtxt* server, XP_U16* channel )
XP_ASSERT( amServer( server ) );
if ( amServer( server ) && 0 < server->nv.pendingRegistrations ) {
const XP_U16 nPlayers = server->vol.gi->nPlayers;
const ServerPlayer* players = server->players;
const ServerPlayer* players = server->srvPlyrs;
for ( int ii = 0; ii < nPlayers && !result; ++ii ) {
result = UNKNOWN_DEVICE == players->deviceIndex;
if ( result ) {
@ -3883,6 +3967,91 @@ server_getOpenChannel( const ServerCtxt* server, XP_U16* channel )
return result;
}
XP_Bool
server_canRematch( const ServerCtxt* server )
{
/* XP_LOGFF( "nDevices: %d; nPlayers: %d", */
/* server->nv.nDevices, server->vol.gi->nPlayers ); */
const CurGameInfo* gi = server->vol.gi;
XP_Bool result;
switch ( gi->serverRole ) {
case SERVER_STANDALONE:
result = XP_TRUE; /* can always rematch a local game */
break;
case SERVER_ISSERVER:
/* have all expected clients connected? */
result = XWSTATE_RECEIVED_ALL_REG <= server->nv.gameState;
break;
case SERVER_ISCLIENT:
result = 2 == gi->nPlayers
? XP_TRUE /* We always have server address */
: 0 < server->nv.rematchAddrsLen;
break;
}
LOG_RETURNF( "%s", boolToStr(result) );
return result;
}
XP_Bool
server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil,
XP_U32 gameID, RematchAddrs* ra )
{
LOG_FUNC();
XP_Bool success = server_canRematch( server );
if ( success ) {
XP_MEMSET( ra, 0, sizeof(*ra) );
CurGameInfo* newGI = newUtil->gameInfo;
gi_disposePlayerInfo( MPPARM(newUtil->mpool) newGI );
gi_copy( MPPARM(newUtil->mpool) newGI, server->vol.gi );
newGI->gameID = gameID;
if ( SERVER_ISCLIENT == newGI->serverRole ) {
newGI->serverRole = SERVER_ISSERVER; /* we'll be inviting */
newGI->forceChannel = 0;
}
LOGGI( newUtil->gameInfo, "ready to invite" );
/* Now build the address list */
const CommsCtxt* comms = server->vol.comms;
if ( amServer( server ) ) {
/* skip 0; it's me */
for ( int ii = 1; ii < server->nv.nDevices; ++ii ) {
XP_PlayerAddr channelNo = server->nv.addresses[ii].channelNo;
comms_getChannelAddr( comms, channelNo, &ra->addrs[ra->nAddrs] );
++ra->nAddrs;
}
} else {
/* first, server's address */
comms_getHostAddr( comms, &ra->addrs[ra->nAddrs++] );
/* then, any other guests we've been told about */
if ( !!server->nv.rematchAddrs ) {
XWStreamCtxt* stream = mkServerStream( server );
stream_setVersion( stream, server->nv.streamVersion );
stream_putBytes( stream, server->nv.rematchAddrs,
server->nv.rematchAddrsLen );
while ( 0 < stream_getSize( stream )
&& ra->nAddrs < VSIZE(ra->addrs) ) {
addrFromStream( &ra->addrs[ra->nAddrs++], stream );
}
}
}
}
#ifdef DEBUG
if ( success ) {
for ( int ii = 0; ii < ra->nAddrs; ++ii ) {
XP_LOGFF( "addr %d of %d: ", ii, ra->nAddrs );
logAddr( MPPARM(server->mpool) server->vol.dutil,
&ra->addrs[ii], NULL );
}
}
#endif
XP_ASSERT( !success || server->vol.gi->nPlayers == ra->nAddrs + 1 );
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}
XP_U32
server_getLastMoveTime( const ServerCtxt* server )
{

View file

@ -1,6 +1,6 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
* Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved.
* Copyright 1997 - 2023 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
@ -25,6 +25,7 @@
#include "commmgr.h"
#include "model.h"
#include "gameinfo.h"
#ifdef CPLUS
extern "C" {
@ -85,6 +86,7 @@ XP_S16 server_getTimerSeconds( const ServerCtxt* server, XWEnv xwe, XP_U16 turn
XP_Bool server_dupTurnDone( const ServerCtxt* server, XP_U16 turn );
XP_Bool server_canPause( const ServerCtxt* server );
XP_Bool server_canUnpause( const ServerCtxt* server );
XP_Bool server_canRematch( const ServerCtxt* server );
void server_pause( ServerCtxt* server, XWEnv xwe, XP_S16 turn, const XP_UCHAR* msg );
void server_unpause( ServerCtxt* server, XWEnv xwe, XP_S16 turn, const XP_UCHAR* msg );
@ -142,6 +144,19 @@ XP_U16 server_figureFinishBonus( const ServerCtxt* server, XP_U16 turn );
XP_Bool server_getIsServer( const ServerCtxt* server );
#endif
typedef struct _RematchAddrs {
CommsAddrRec addrs[MAX_NUM_PLAYERS];
XP_U16 nAddrs;
} RematchAddrs;
/* Sets up newUtil->gameInfo correctly, and returns with a set of
addresses to which invitation should be sent. But: meant to be called
only from game.c anyway.
*/
XP_Bool server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil,
XP_U32 gameID, RematchAddrs* ra );
#ifdef CPLUS
}
#endif

View file

@ -1259,7 +1259,8 @@ inviteList( CommonGlobals* cGlobals, CommsAddrRec* myAddr, GSList* invitees,
XP_ASSERT(0);
}
#ifdef XWFEATURE_COMMS_INVITE
comms_invite( cGlobals->game.comms, NULL_XWE, &nli, &destAddr, XP_TRUE );
comms_invite( cGlobals->game.comms, NULL_XWE, &nli,
&destAddr, XP_TRUE );
#endif
}
}

View file

@ -366,8 +366,11 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
XW_UtilCtxt* util = newGlobals->cGlobals.util;
const CommonPrefs* cp = &newGlobals->cGlobals.cp;
XP_UCHAR buf[64];
snprintf( buf, VSIZE(buf), "Game %lX", XP_RANDOM() % 256 );
game_makeRematch( &cGlobals->game, NULL_XWE, util, cp,
&newGlobals->cGlobals.game, NULL, XP_TRUE );
&newGlobals->cGlobals.procs,
&newGlobals->cGlobals.game, buf );
linuxSaveGame( &newGlobals->cGlobals );
sqlite3_int64 rowid = newGlobals->cGlobals.rowid;