Change the suggested game name (a vs b) with the RematchOrder

It's bad to display a name based on a player order in the same place
as where you're letting people change that order and not have the
name change. So re-juggle the rematch process to allow the name to
be changed -- but don't change once the user has edited the name.
This commit is contained in:
Eric House 2024-01-15 09:22:37 -08:00
parent 144b68cbd5
commit aa0aded8c4
19 changed files with 480 additions and 207 deletions

View file

@ -446,15 +446,10 @@ public class GameUtils {
public static Bitmap loadMakeBitmap( Context context, long rowid )
{
Bitmap thumb = null;
try ( GameLock lock = GameLock.tryLockRO( rowid ) ) {
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( context );
try ( GamePtr gamePtr = loadMakeGame( context, gi, lock ) ) {
if ( null != gamePtr ) {
thumb = takeSnapshot( context, gamePtr, gi );
DBUtils.saveThumbnail( context, lock, thumb );
}
}
try ( GameWrapper gw = makeGameWrapper( context, rowid ) ) {
if ( null != gw ) {
thumb = takeSnapshot( context, gw.gamePtr(), gw.gi() );
DBUtils.saveThumbnail( context, gw.lock(), thumb );
}
}
return thumb;
@ -574,25 +569,71 @@ public class GameUtils {
return rowid;
}
public static class GameWrapper implements AutoCloseable {
private Context mContext;
private GameLock mLock;
private GamePtr mGamePtr;
private CurGameInfo mGi;
GameWrapper( Context context, long rowid )
{
mContext = context;
mLock = GameLock.tryLockRO( rowid );
if ( null != mLock ) {
mGi = new CurGameInfo( mContext );
mGamePtr = loadMakeGame( mContext, mGi, mLock );
}
}
public GamePtr gamePtr() { return mGamePtr; }
public GameLock lock() { return mLock; }
public CurGameInfo gi() { return mGi; }
public boolean hasGame() { return null != mGamePtr; }
@Override
public void close()
{
if ( null != mGamePtr ) {
mGamePtr.close();
mGamePtr = null;
}
if ( null != mLock ) {
mLock.close();
mLock = null;
}
}
@Override
public void finalize() throws java.lang.Throwable
{
close();
}
}
public static GameWrapper makeGameWrapper( Context context, long rowid )
{
GameWrapper result = new GameWrapper( context, rowid );
if ( !result.hasGame() ) {
result.close();
result = null;
}
return result;
}
public static long makeRematch( Context context, long srcRowid,
long groupID, String gameName,
RematchOrder ro )
int[] newOrder )
{
long rowid = DBUtils.ROWID_NOTFOUND;
try ( GameLock lock = GameLock.tryLockRO( srcRowid ) ) {
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( context );
try ( GamePtr gamePtr = loadMakeGame( context, gi, lock ) ) {
if ( null != gamePtr ) {
UtilCtxt util = new UtilCtxtImpl( context );
CommonPrefs cp = CommonPrefs.get( context );
try ( GamePtr gamePtrNew = XwJNI
.game_makeRematch( gamePtr, util, cp, gameName, ro ) ) {
if ( null != gamePtrNew ) {
rowid = saveNewGame1( context, gamePtrNew,
groupID, gameName );
}
}
try ( GameWrapper gw = makeGameWrapper( context, srcRowid ) ) {
if ( null != gw ) {
UtilCtxt util = new UtilCtxtImpl( context );
CommonPrefs cp = CommonPrefs.get( context );
try ( GamePtr gamePtrNew = XwJNI
.game_makeRematch( gw.gamePtr(), util, cp, gameName, newOrder ) ) {
if ( null != gamePtrNew ) {
rowid = saveNewGame1( context, gamePtrNew,
groupID, gameName );
}
}
}

View file

@ -880,13 +880,11 @@ public class GamesListDelegate extends ListDelegateBase
LocUtils.inflate( m_activity, R.layout.rematch_config );
int iconResID = R.drawable.ic_sologame;
Assert.assertTrueNR( null != m_rematchExtras );
if ( null != m_rematchExtras ) {
long rowid = m_rematchExtras.getLong( REMATCH_ROWID_EXTRA,
ROWID_NOTFOUND );
GameSummary summary = GameUtils.getSummary( m_activity, rowid );
view.setName( m_rematchExtras.getString( REMATCH_NEWNAME_EXTRA ) )
.setCanOfferRO( summary.canOfferRO );
view.configure( rowid, this );
solo = m_rematchExtras.getBoolean( REMATCH_IS_SOLO, true );
if ( !solo ) {
iconResID = R.drawable.ic_multigame;
@ -900,7 +898,7 @@ public class GamesListDelegate extends ListDelegateBase
.setPositiveButton( android.R.string.ok, new OnClickListener() {
@Override
public void onClick( DialogInterface dlg, int item ) {
startRematchWithName( view.getName(), view.getRO(), true );
startRematchWithName( view.getName(), view.getNewOrder(), true );
}
} )
.setNegativeButton( android.R.string.cancel, null )
@ -2684,7 +2682,7 @@ public class GamesListDelegate extends ListDelegateBase
}
private void startRematchWithName( final String gameName,
final RematchOrder ro,
final int[] newOrder,
boolean showRationale )
{
if ( null != gameName && 0 < gameName.length() ) {
@ -2694,7 +2692,7 @@ public class GamesListDelegate extends ListDelegateBase
final CommsConnTypeSet addrs = new CommsConnTypeSet( bits );
boolean hasSMS = addrs.contains( CommsConnType.COMMS_CONN_SMS );
if ( !hasSMS || null != SMSPhoneInfo.get( m_activity ) ) {
rematchWithNameAndPerm( gameName, ro, addrs );
rematchWithNameAndPerm( gameName, newOrder, addrs );
} else {
int id = (1 == addrs.size())
? R.string.phone_lookup_rationale_drop
@ -2702,7 +2700,7 @@ public class GamesListDelegate extends ListDelegateBase
String msg = getString( R.string.phone_lookup_rationale )
+ "\n\n" + getString( id );
Perms23.tryGetPerms( this, Perms23.NBS_PERMS, msg,
Action.ASKED_PHONE_STATE, gameName, ro, addrs );
Action.ASKED_PHONE_STATE, gameName, newOrder, addrs );
}
}
}
@ -2714,11 +2712,11 @@ public class GamesListDelegate extends ListDelegateBase
addrs.remove( CommsConnType.COMMS_CONN_SMS );
}
if ( 0 < addrs.size() ) {
rematchWithNameAndPerm( (String)params[0], (RematchOrder)params[1], addrs );
rematchWithNameAndPerm( (String)params[0], (int[])params[1], addrs );
}
}
private void rematchWithNameAndPerm( String gameName, RematchOrder ro,
private void rematchWithNameAndPerm( String gameName, int[] newOrder,
CommsConnTypeSet addrs )
{
if ( null != gameName && 0 < gameName.length() ) {
@ -2729,7 +2727,7 @@ public class GamesListDelegate extends ListDelegateBase
DBUtils.GROUPID_UNSPEC );
long newid = GameUtils.makeRematch( m_activity, srcRowID,
groupID, gameName, ro );
groupID, gameName, newOrder );
if ( DBUtils.ROWID_NOTFOUND != newid ) {
if ( extras.getBoolean( REMATCH_DELAFTER_EXTRA, false ) ) {

View file

@ -21,6 +21,7 @@
package org.eehouse.android.xw4;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView.OnItemSelectedListener;
@ -33,17 +34,29 @@ import android.widget.RadioGroup;
import java.util.HashMap;
import java.util.Map;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
import org.eehouse.android.xw4.loc.LocUtils;
public class RematchConfigView extends LinearLayout
implements RadioGroup.OnCheckedChangeListener
{
private static final String TAG = RematchConfigView.class.getSimpleName();
private static final String KEY_LAST_RO = TAG + "/key_last_ro";
private Context mContext;
private DlgDelegate.HasDlgDelegate mDlgDlgt;
private RadioGroup mGroup;
Map<Integer, RematchOrder> mRos = new HashMap<>();
private GameUtils.GameWrapper mWrapper;
private Map<Integer, RematchOrder> mRos = new HashMap<>();
private boolean mInflated;
private String mNameStr;
private int[] mNewOrder;
private String mSep;
private boolean mUserEditing = false;
private boolean mNAShown;
private EditWClear mEWC;
private RematchOrder mCurRO;
public RematchConfigView( Context cx, AttributeSet as )
{
@ -51,53 +64,90 @@ public class RematchConfigView extends LinearLayout
mContext = cx;
}
public RematchConfigView setName( String name )
public void configure( long rowid, DlgDelegate.HasDlgDelegate dlgDlgt )
{
EditWClear ewc = (EditWClear)findViewById( R.id.name );
ewc.setText( name );
return this;
mDlgDlgt = dlgDlgt;
mWrapper = GameUtils.makeGameWrapper( mContext, rowid );
trySetup();
}
public String getName()
{
EditWClear ewc = (EditWClear)findViewById( R.id.name );
return ewc.getText().toString();
}
public RematchConfigView setCanOfferRO( boolean canOfferRO )
{
if ( !canOfferRO ) {
findViewById( R.id.ro_stuff ).setVisibility( View.GONE );
}
return this;
return mEWC.getText().toString();
}
@Override
protected void onFinishInflate()
{
mGroup = (RadioGroup)findViewById( R.id.group );
mInflated = true;
trySetup();
}
int ordinal = DBUtils.getIntFor( mContext, KEY_LAST_RO, 0 );
RematchOrder lastSel = RematchOrder.values()[ordinal];
@Override
protected void onDetachedFromWindow()
{
if ( null != mWrapper ) {
mWrapper.close();
mWrapper = null;
}
super.onDetachedFromWindow();
}
for ( RematchOrder ro : RematchOrder.values() ) {
RadioButton button = new RadioButton( mContext );
button.setText( LocUtils.getString( mContext, ro.getStrID() ) );
mGroup.addView( button );
mRos.put( button.getId(), ro );
if ( lastSel == ro ) {
button.setChecked( true );
// RadioGroup.OnCheckedChangeListener
@Override
public void onCheckedChanged( RadioGroup group, int checkedId )
{
if ( !mUserEditing && null != mNameStr ) {
mUserEditing = ! mNameStr.equals( getName() );
}
mCurRO = mRos.get( checkedId );
mNewOrder = XwJNI.server_figureOrder( mWrapper.gamePtr(), mCurRO );
if ( mUserEditing ) {
if ( !mNAShown ) {
mNAShown = true;
mDlgDlgt.makeNotAgainBuilder( R.string.key_na_rematch_edit,
R.string.na_rematch_edit )
.show();
}
} else {
mNameStr = TextUtils.join( mSep, mWrapper.gi().playerNames(mNewOrder) );
Log.d( TAG, "mNameStr: %s", mNameStr );
mEWC.setText( mNameStr );
}
}
public int[] getNewOrder()
{
DBUtils.setIntFor( mContext, KEY_LAST_RO, mCurRO.ordinal() );
return mNewOrder;
}
private void trySetup()
{
if ( mInflated && null != mWrapper ) {
mSep = LocUtils.getString( mContext, R.string.vs_join );
mGroup = (RadioGroup)findViewById( R.id.group );
mGroup.setOnCheckedChangeListener( this );
mEWC = (EditWClear)findViewById( R.id.name );
boolean[] results = XwJNI.server_canOfferRematch( mWrapper.gamePtr() );
if ( results[0] && results[1] ) {
int ordinal = DBUtils.getIntFor( mContext, KEY_LAST_RO, 0 );
RematchOrder lastSel = RematchOrder.values()[ordinal];
for ( RematchOrder ro : RematchOrder.values() ) {
RadioButton button = new RadioButton( mContext );
button.setText( LocUtils.getString( mContext, ro.getStrID() ) );
mGroup.addView( button );
mRos.put( button.getId(), ro );
if ( lastSel == ro ) {
button.setChecked( true );
}
}
}
}
}
public RematchOrder getRO()
{
int id = mGroup.getCheckedRadioButtonId();
RematchOrder ro = mRos.get(id);
DBUtils.setIntFor( mContext, KEY_LAST_RO, ro.ordinal() );
return ro;
}
}

View file

@ -413,10 +413,16 @@ public class CurGameInfo implements Serializable {
}
public String[] playerNames()
{
return playerNames( null );
}
public String[] playerNames( int[] newOrder )
{
String[] names = new String[nPlayers];
for ( int ii = 0; ii < nPlayers; ++ii ) {
names[ii] = players[ii].name;
int indx = null == newOrder ? ii : newOrder[ii];
names[ii] = players[indx].name;
}
return names;
}

View file

@ -351,10 +351,10 @@ public class XwJNI {
public static GamePtr game_makeRematch( GamePtr gamePtr, UtilCtxt util,
CommonPrefs cp, String gameName,
RematchOrder ro )
int[] newOrder )
{
GamePtr gamePtrNew = initGameJNI( 0 );
if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName, ro ) ) {
if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName, newOrder ) ) {
gamePtrNew.release();
gamePtrNew = null;
}
@ -398,7 +398,7 @@ public class XwJNI {
private static native boolean game_makeRematch( GamePtr gamePtr,
GamePtr gamePtrNew,
UtilCtxt util, CommonPrefs cp,
String gameName, RematchOrder ro );
String gameName, int[] newOrder );
private static native boolean game_makeFromInvite( GamePtr gamePtr, NetLaunchInfo nli,
UtilCtxt util,
@ -536,7 +536,14 @@ public class XwJNI {
public static native boolean server_getGameIsConnected( GamePtr gamePtr );
public static native String server_writeFinalScores( GamePtr gamePtr );
public static native boolean server_initClientConnection( GamePtr gamePtr );
public static native boolean server_canOfferRematch( GamePtr gamePtr );
public static boolean[] server_canOfferRematch( GamePtr gamePtr )
{
boolean[] results = {false, false};
server_canOfferRematch( gamePtr, results );
return results;
}
private static native void server_canOfferRematch( GamePtr gamePtr, boolean[] results );
public static native int[] server_figureOrder( GamePtr gamePtr, RematchOrder ro );
public static native void server_endGame( GamePtr gamePtr );
// hybrid to save work

View file

@ -176,6 +176,7 @@
<string name="key_na_deletecheck">key_na_deletecheck</string>
<string name="key_theme_which">key_theme_which</string>
<string name="key_na_rematch_edit">key_na_rematch_edit</string>
<!-- Nor is my email address -->
<string name="email_author_email">xwords@eehouse.org</string>

View file

@ -5,4 +5,8 @@
<string name="dup_allscores_fmt">All scores: %1$s</string>
<string name="na_rematch_edit">The game name will not change once
you have started editing it. If you decide you want a generated
name, please cancel and restart the rematching process.</string>
</resources>

View file

@ -397,10 +397,15 @@ makeBooleanArray( JNIEnv* env, int siz, const jboolean* vals )
return array;
}
void
int
getIntsFromArray( JNIEnv* env, int dest[], jintArray arr, int count, bool del )
{
jint* ints = (*env)->GetIntArrayElements(env, arr, 0);
jsize len = (*env)->GetArrayLength( env, arr );
if ( len < count ) {
count = len;
}
for ( int ii = 0; ii < count; ++ii ) {
dest[ii] = ints[ii];
}
@ -408,6 +413,7 @@ getIntsFromArray( JNIEnv* env, int dest[], jintArray arr, int count, bool del )
if ( del ) {
deleteLocalRef( env, arr );
}
return count;
}
void

View file

@ -71,7 +71,8 @@ jobject getObjectField( JNIEnv* env, jobject obj, const char* fieldName,
jintArray makeIntArray( JNIEnv* env, int size, const void* vals, size_t elemSize );
void setIntArray( JNIEnv* env, jobject jowner, const char* ownerField,
int count, const void* vals, size_t elemSize );
void getIntsFromArray( JNIEnv* env, int dest[], jintArray arr, int count, bool del );
/* returns number of items copied, i.e. min of count and len(array) */
int getIntsFromArray( JNIEnv* env, int dest[], jintArray arr, int count, bool del );
void setIntInArray( JNIEnv* env, jintArray arr, int index, int val );
jbyteArray makeByteArray( JNIEnv* env, int size, const jbyte* vals );

View file

@ -1423,7 +1423,7 @@ initGameGlobals( JNIEnv* env, JNIState* state, jobject jutil, jobject jprocs )
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, jobject jRo )
jobject jutil, jobject jcp, jstring jGameName, jintArray jNO )
{
jboolean success = false;
XWJNI_START_GLOBALS(gamePtr);
@ -1437,10 +1437,17 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch
loadCommonPrefs( env, &cp, jcp );
const char* gameName = (*env)->GetStringUTFChars( env, jGameName, NULL );
RematchOrder ro = jEnumToInt( env, jRo );
NewOrder no;
int tmp[VSIZE(no.order)];
int count = getIntsFromArray( env, tmp, jNO, VSIZE(tmp), XP_FALSE );
for ( int ii = 0; ii < count; ++ii ) {
no.order[ii] = tmp[ii];
}
success = game_makeRematch( &oldState->game, env, globals->util, &cp,
(TransportProcs*)NULL, &state->game,
gameName, ro );
gameName, &no );
(*env)->ReleaseStringUTFChars( env, jGameName, gameName );
if ( success ) {
@ -2191,15 +2198,32 @@ Java_org_eehouse_android_xw4_jni_XwJNI_server_1initClientConnection
return result;
}
JNIEXPORT jboolean JNICALL
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_server_1canOfferRematch
( JNIEnv* env, jclass C, GamePtrType gamePtr )
( JNIEnv* env, jclass C, GamePtrType gamePtr, jbooleanArray results )
{
jboolean result;
XWJNI_START_GLOBALS(gamePtr);
XP_Bool canOffer;
XP_Bool canRematch = server_canRematch( state->game.server, &canOffer );
result = canRematch && canOffer;
XP_Bool bools[2];
bools[0] = server_canRematch( state->game.server, &bools[1] );
setBoolArray( env, results, VSIZE(bools), (jboolean*)bools );
XWJNI_END();
}
JNIEXPORT jintArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_server_1figureOrder
( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jRo )
{
jintArray result = NULL;
XWJNI_START_GLOBALS(gamePtr);
RematchOrder ro = jEnumToInt( env, jRo );
XP_LOGFF( "(ro=%s)", RO2Str(ro) );
NewOrder no;
server_figureOrder( state->game.server, ro, &no );
result = makeIntArray( env, globals->gi->nPlayers, no.order, sizeof(no.order[0]) );
XWJNI_END();
return result;
}

View file

@ -231,14 +231,13 @@ game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
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 )
XWGame* newGame, const XP_UCHAR* newName, NewOrder* nop )
{
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 ) ) {
makeGameID( newUtil ), nop, &rip ) ) {
CommsAddrRec* selfAddrP = NULL;
CommsAddrRec selfAddr;
if ( !!oldGame->comms ) {
@ -275,9 +274,10 @@ game_makeRematch( const XWGame* oldGame, XWEnv xwe, XW_UtilCtxt* newUtil,
}
server_disposeRematchInfo( oldGame->server, &rip );
}
XP_LOGFF( "=> %s; game with gid %08X rematched to create game with gid %08X using ro %s",
XP_LOGFF( "=> %s; game with gid %08X rematched to create game "
"with gid %08X",
boolToStr(success), oldGame->util->gameInfo->gameID,
newUtil->gameInfo->gameID, RO2Str(ro) );
newUtil->gameInfo->gameID );
return success;
}

View file

@ -85,7 +85,7 @@ XP_Bool game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
XP_Bool game_makeRematch( const XWGame* game, XWEnv xwe, XW_UtilCtxt* util,
const CommonPrefs* cp, const TransportProcs* procs,
XWGame* newGame, const XP_UCHAR* newName,
RematchOrder ro );
NewOrder* no );
void game_changeDict( MPFORMAL XWGame* game, XWEnv xwe, CurGameInfo* gi,
DictionaryCtxt* dict );

View file

@ -4184,16 +4184,16 @@ server_canRematch( const ServerCtxt* server, XP_Bool* canOrderP )
as invitees join the new game.
*/
static void
sortBySame( const ServerCtxt* server, int newOrder[] )
sortBySame( const ServerCtxt* server, NewOrder* nop )
{
const CurGameInfo* gi = server->vol.gi;
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
newOrder[ii] = ii;
nop->order[ii] = ii;
}
}
static void
sortByScoreLow( const ServerCtxt* server, int newOrder[] )
sortByScoreLow( const ServerCtxt* server, NewOrder* nop )
{
const CurGameInfo* gi = server->vol.gi;
@ -4216,7 +4216,7 @@ sortByScoreLow( const ServerCtxt* server, int newOrder[] )
break;
} else {
mask |= 1 << newPosn;
newOrder[resultIndx] = newPosn;
nop->order[resultIndx] = newPosn;
/* SRVR_LOGFF( "result[%d] = %d (for score %d)", resultIndx, newPosn, */
/* lowest ); */
}
@ -4224,20 +4224,20 @@ sortByScoreLow( const ServerCtxt* server, int newOrder[] )
}
static void
sortByScoreHigh( const ServerCtxt* server, int newOrder[] )
sortByScoreHigh( const ServerCtxt* server, NewOrder* nop )
{
sortByScoreLow( server, newOrder );
sortByScoreLow( server, nop );
const CurGameInfo* gi = server->vol.gi;
for ( int ii = 0, jj = gi->nPlayers - 1; ii < jj; ++ii, --jj ) {
int tmp = newOrder[ii];
newOrder[ii] = newOrder[jj];
newOrder[jj] = tmp;
int tmp = nop->order[ii];
nop->order[ii] = nop->order[jj];
nop->order[jj] = tmp;
}
}
static void
sortByRandom( const ServerCtxt* server, int newOrder[] )
sortByRandom( const ServerCtxt* server, NewOrder* nop )
{
const CurGameInfo* gi = server->vol.gi;
int src[gi->nPlayers];
@ -4247,8 +4247,8 @@ sortByRandom( const ServerCtxt* server, int newOrder[] )
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
int nLeft = gi->nPlayers - ii;
int indx = XP_RANDOM() % nLeft;
newOrder[ii] = src[indx];
SRVR_LOGFF( "set result[%d] to %d", ii, newOrder[ii] );
nop->order[ii] = src[indx];
SRVR_LOGFF( "set result[%d] to %d", ii, nop->order[ii] );
/* now swap the last down */
src[indx] = src[nLeft-1];
}
@ -4256,7 +4256,7 @@ sortByRandom( const ServerCtxt* server, int newOrder[] )
#ifdef XWFEATURE_RO_BYNAME
static void
sortByName( const ServerCtxt* server, int newOrder[] )
sortByName( const ServerCtxt* server, NewOrder* nop )
{
const CurGameInfo* gi = server->vol.gi;
int mask = 0; /* mark values already consumed */
@ -4275,81 +4275,40 @@ sortByName( const ServerCtxt* server, int newOrder[] )
}
XP_ASSERT( lowest != -1 );
mask |= 1 << lowest;
newOrder[ii] = lowest;
nop->order[ii] = lowest;
}
}
#endif
typedef void (*OrderProc)(const ServerCtxt* server, int newOrder[]);
static XP_Bool
setPlayerOrder( const ServerCtxt* server, RematchOrder ro,
setPlayerOrder( const ServerCtxt* server, const NewOrder* nop,
CurGameInfo* gi, RematchInfo* rip )
{
// SRVR_LOGFF( "(ro=%s)", RO2Str(ri->ro) );
LOGGI( gi, "start" );
OrderProc proc = NULL;
switch ( ro ) {
case RO_SAME:
proc = sortBySame;
// sortBySame( server, newOrder );
break;
case RO_LOW_SCORE_FIRST:
proc = sortByScoreLow;
break;
case RO_HIGH_SCORE_FIRST:
proc = sortByScoreHigh;
break;
case RO_JUGGLE:
proc = sortByRandom;
break;
#ifdef XWFEATURE_RO_BYNAME
case RO_BY_NAME:
proc = sortByName;
break;
#endif
case RO_NUM_ROS:
default:
XP_ASSERT(0); break;
CurGameInfo srcGi = *gi;
RematchInfo srcRi;
if ( !!rip ) {
srcRi = *rip;
}
XP_ASSERT( !!proc );
int newOrder[gi->nPlayers];
XP_MEMSET( newOrder, 0, sizeof(newOrder) );
XP_Bool success = !!proc;
if ( success ) {
(*proc)( server, newOrder );
/* We have gi and rip that express an ordering of players. And we have
a new order into which to move them. Just walk and swap the current
with the right one above it. */
CurGameInfo srcGi = *gi;
RematchInfo srcRi;
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
gi->players[ii] = srcGi.players[nop->order[ii]];
if ( !!rip ) {
srcRi = *rip;
}
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
gi->players[ii] = srcGi.players[newOrder[ii]];
if ( !!rip ) {
rip->addrIndices[ii] = srcRi.addrIndices[newOrder[ii]];
}
}
LOGGI( gi, "end" );
if ( !!rip ) {
LOG_RI( rip );
rip->addrIndices[ii] = srcRi.addrIndices[nop->order[ii]];
}
}
XP_ASSERT(success);
return success;
LOGGI( gi, "end" );
if ( !!rip ) {
LOG_RI( rip );
}
return XP_TRUE;
} /* setPlayerOrder */
XP_Bool
server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil,
XP_U32 gameID, RematchOrder ro, RematchInfo** ripp )
XP_U32 gameID, const NewOrder* nop, RematchInfo** ripp )
{
SRVR_LOGFF( "(ro=%s)", RO2Str(ro) );
XP_Bool success = server_canRematch( server, NULL );
const CommsCtxt* comms = server->vol.comms;
if ( success ) {
@ -4443,7 +4402,7 @@ server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil,
if ( !!comms ) {
assertRI( &ri, newGI );
}
success = setPlayerOrder( server, ro, newGI, !!comms ? &ri : NULL );
success = setPlayerOrder( server, nop, newGI, !!comms ? &ri : NULL );
}
if ( success && !!comms ) {
@ -4500,6 +4459,38 @@ server_ri_getAddr( const RematchInfo* rip, XP_U16 nth,
return success;
}
void
server_figureOrder( const ServerCtxt* server, RematchOrder ro, NewOrder* nop )
{
XP_MEMSET( nop, 0, sizeof(*nop) );
void (*proc)(const ServerCtxt*, NewOrder*) = NULL;
switch ( ro ) {
case RO_SAME:
proc = sortBySame;
break;
case RO_LOW_SCORE_FIRST:
proc = sortByScoreLow;
break;
case RO_HIGH_SCORE_FIRST:
proc = sortByScoreHigh;
break;
case RO_JUGGLE:
proc = sortByRandom;
break;
#ifdef XWFEATURE_RO_BYNAME
case RO_BY_NAME:
proc = sortByName;
break;
#endif
case RO_NUM_ROS:
default:
XP_ASSERT(0); break;
}
(*proc)( server, nop );
}
/* Record the desired order, which is already set in the RematchInfo passed
in, so we can enforce it as clients register. */
void

View file

@ -167,13 +167,22 @@ const XP_UCHAR* RO2Str(RematchOrder ro);
No need for a count: once we find a playersMask == 0 we're done
*/
typedef struct RematchInfo RematchInfo;
typedef struct _NewOrder {
XP_U8 order[MAX_NUM_PLAYERS];
} NewOrder;
/* Figure the order of players from the current game per the RematchOrder
provided. */
void server_figureOrder( const ServerCtxt* server, RematchOrder ro,
NewOrder* nop );
/* 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.
*/
typedef struct RematchInfo RematchInfo;
XP_Bool server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil,
XP_U32 gameID, RematchOrder ro, RematchInfo** ripp );
XP_U32 gameID, const NewOrder* nop, RematchInfo** ripp );
void server_disposeRematchInfo( ServerCtxt* server, RematchInfo** rip );
XP_Bool server_ri_getAddr( const RematchInfo* ri, XP_U16 nth,
CommsAddrRec* addr, XP_U16* nPlayersH );

View file

@ -130,6 +130,7 @@ DEFINES += -DINITIAL_CLIENT_VERS=3
DEFINES += -DCOMMON_LAYOUT
DEFINES += -DNATIVE_NLI
DEFINES += -DXWFEATURE_COMMS_INVITE
DEFINES += -DXWFEATURE_RO_BYNAME
# DEFINES += -DRELAY_VIA_HTTP
# MAX_ROWS controls STREAM_VERS_BIGBOARD and with it move hashing
@ -211,6 +212,7 @@ GTK_OBJS = \
$(BUILD_PLAT_DIR)/gtkaskdict.o \
$(BUILD_PLAT_DIR)/gtkchat.o \
$(BUILD_PLAT_DIR)/gtkkpdlg.o \
$(BUILD_PLAT_DIR)/gtkrmtch.o \
endif
ifdef DO_CURSES

View file

@ -1032,10 +1032,13 @@ rematch_and_save( CursesBoardGlobals* bGlobals, RematchOrder ro,
CursesBoardGlobals* bGlobalsNew = commonInit( cbState, -1, NULL );
XP_Bool success = game_makeRematch( &bGlobals->cGlobals.game, NULL_XWE,
NewOrder no;
server_figureOrder( cGlobals->game.server, ro, &no );
XP_Bool success = game_makeRematch( &cGlobals->game, NULL_XWE,
bGlobalsNew->cGlobals.util,
&cGlobals->cp, &bGlobalsNew->cGlobals.procs,
&bGlobalsNew->cGlobals.game, "newName", ro );
&bGlobalsNew->cGlobals.game, "newName", &no );
if ( success ) {
if ( !!newGameIDP ) {
*newGameIDP = bGlobalsNew->cGlobals.gi->gameID;

View file

@ -35,6 +35,7 @@
#include "device.h"
#include "gtkkpdlg.h"
#include "gtknewgame.h"
#include "gtkrmtch.h"
static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid,
XP_Bool isNew );
@ -366,51 +367,33 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure )
void
make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
{
XP_Bool canOffer;
XP_Bool canRematch = server_canRematch( cGlobals->game.server, &canOffer );
XP_Bool canRematch = server_canRematch( cGlobals->game.server, NULL );
XP_ASSERT( canRematch );
RematchOrder ro = RO_SAME;
if ( canOffer ) {
const AskPair buttons[] = {
{"Juggle", RO_JUGGLE},
{"Low score first", RO_LOW_SCORE_FIRST},
{"High score first", RO_HIGH_SCORE_FIRST},
#ifdef XWFEATURE_RO_BYNAME
{ "Alphabetical", RO_BY_NAME },
#endif
{ "Keep existing", RO_SAME },
{ NULL, 0 }
};
if ( canRematch ) {
gchar gameName[128];
int nameLen = VSIZE(gameName);
NewOrder no;
if ( gtkask_rematch( cGlobals, &no, gameName, &nameLen ) ) {
LaunchParams* params = apg->cag.params;
GtkGameGlobals* newGlobals = calloc( 1, sizeof(*newGlobals) );
initBoardGlobalsGtk( newGlobals, params, NULL );
gint response;
if ( gtkask_radios( apg->window, "rematch? choose new order",
buttons, &response ) ) {
ro = buttons[response].result;
} else {
goto exit;
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.procs,
&newGlobals->cGlobals.game, buf, &no );
linuxSaveGame( &newGlobals->cGlobals );
sqlite3_int64 rowid = newGlobals->cGlobals.rowid;
freeGlobals( newGlobals );
open_row( apg, rowid, XP_TRUE );
}
}
LaunchParams* params = apg->cag.params;
GtkGameGlobals* newGlobals = calloc( 1, sizeof(*newGlobals) );
initBoardGlobalsGtk( newGlobals, params, NULL );
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.procs,
&newGlobals->cGlobals.game, buf, ro );
linuxSaveGame( &newGlobals->cGlobals );
sqlite3_int64 rowid = newGlobals->cGlobals.rowid;
freeGlobals( newGlobals );
open_row( apg, rowid, XP_TRUE );
exit:
return;
} /* make_rematch */
static void

118
xwords4/linux/gtkrmtch.c Normal file
View file

@ -0,0 +1,118 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
* Copyright 2024 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 "gtkrmtch.h"
#include "dbgutil.h"
#include "gtkutils.h"
struct {
const gchar* txt;
RematchOrder ro;
} sROData[] = {
{ "Keep existing", RO_SAME },
{ "Low score first", RO_LOW_SCORE_FIRST },
{ "High score first", RO_HIGH_SCORE_FIRST },
{ "Juggle", RO_JUGGLE },
#ifdef XWFEATURE_RO_BYNAME
{ "Alphabetical", RO_BY_NAME },
#endif
};
#define OK_RESULT 1000
typedef struct _State {
const CommonGlobals* cGlobals;
NewOrder* nop;
GtkWidget* dialog;
GtkWidget* radios[RO_NUM_ROS];
int curSel;
GtkWidget* nameField;
} State;
static void
toggled( GtkToggleButton* togglebutton, gpointer user_data )
{
State* state = (State*)user_data;
gboolean active = gtk_toggle_button_get_active( togglebutton );
if ( active ) {
for ( int ii = 0; ii < VSIZE(sROData); ++ii ) {
if ( state->radios[ii] == GTK_WIDGET(togglebutton) ) {
state->curSel = ii;
server_figureOrder( state->cGlobals->game.server, sROData[ii].ro,
state->nop );
const CurGameInfo* gi = state->cGlobals->gi;
const gchar* arr[gi->nPlayers + 1];
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
arr[ii] = gi->players[state->nop->order[ii]].name;
}
arr[gi->nPlayers] = NULL;
gchar* namesstr = g_strjoinv( " vs. ", (gchar**)arr );
gtk_entry_set_text( GTK_ENTRY(state->nameField), namesstr );
g_free( namesstr );
break;
}
}
}
}
XP_Bool
gtkask_rematch( const CommonGlobals* cGlobals, NewOrder* nop,
gchar* gameName, int* nameLen )
{
XP_USE( gameName );
XP_USE( nameLen );
State state = { .cGlobals = cGlobals, .nop = nop, };
state.dialog = gtk_dialog_new();
gtk_window_set_modal( GTK_WINDOW( state.dialog ), TRUE );
GtkWidget* vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
GtkWidget* tmp = makeLabeledField( "Game Name", &state.nameField, "nothing" );
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(tmp), FALSE, TRUE, 0 );
GtkWidget* prev = NULL;
for ( int ii = 0; ii < VSIZE(sROData); ++ii ) {
const gchar* txt = sROData[ii].txt;
GtkWidget* radio = state.radios[ii]
= gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(prev), txt );
g_signal_connect( radio, "toggled", G_CALLBACK(toggled), &state );
gtk_box_pack_start( GTK_BOX(vbox), GTK_WIDGET(radio), FALSE, TRUE, 0 );
prev = radio;
}
g_signal_emit_by_name(state.radios[0], "toggled");
gtk_dialog_add_action_widget( GTK_DIALOG(state.dialog), vbox, 0 );
gtk_dialog_add_button( GTK_DIALOG(state.dialog), "OK", OK_RESULT );
gtk_widget_show_all( state.dialog );
gint dlgResult = gtk_dialog_run( GTK_DIALOG(state.dialog) );
gtk_widget_destroy( state.dialog );
XP_Bool success = dlgResult == OK_RESULT;
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}

29
xwords4/linux/gtkrmtch.h Normal file
View file

@ -0,0 +1,29 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
* Copyright 2024 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.
*/
#ifndef _GTKRMTCH_H_
#define _GTKRMTCH_H_
#include "server.h"
#include "main.h"
XP_Bool gtkask_rematch( const CommonGlobals* cGlobals, NewOrder* nop,
gchar* gameName, int* nameLen );
#endif