Add option to choose how rematch-game players will be ordered

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.)
This commit is contained in:
Eric House 2023-12-18 17:08:51 -08:00
parent 2936869b45
commit 1181e908dc
39 changed files with 1354 additions and 253 deletions

View file

@ -65,6 +65,7 @@ public enum DlgID {
GAMES_LIST_NEWGAME, GAMES_LIST_NEWGAME,
CHANGE_CONN, CHANGE_CONN,
GAMES_LIST_NAME_REMATCH, GAMES_LIST_NAME_REMATCH,
GAMES_LIST_GET_RO,
ASK_DUP_PAUSE, ASK_DUP_PAUSE,
CHOOSE_TILES, CHOOSE_TILES,
SHOW_TILES, SHOW_TILES,

View file

@ -52,6 +52,7 @@ import org.eehouse.android.xw4.jni.UtilCtxt;
import org.eehouse.android.xw4.jni.UtilCtxtImpl; import org.eehouse.android.xw4.jni.UtilCtxtImpl;
import org.eehouse.android.xw4.jni.XwJNI; import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.XwJNI.GamePtr; import org.eehouse.android.xw4.jni.XwJNI.GamePtr;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
import org.eehouse.android.xw4.Utils.ISOCode; import org.eehouse.android.xw4.Utils.ISOCode;
@ -72,6 +73,14 @@ public class GameUtils {
void onResendDone( Context context, int numSent ); void onResendDone( Context context, int numSent );
} }
interface NeedRematchOrder {
// Return null if unable to produce it immediately. Implementation may
// want to start a query at the same time and from its
// ok-button-handler call makeRematch() again with a different
// implementation that simply returns a cached RematchOrder
RematchOrder getRematchOrder();
}
private static Integer s_minScreen; private static Integer s_minScreen;
// Used to determine whether to resend all messages on networking coming // Used to determine whether to resend all messages on networking coming
// back up. The length of the array determines the number of times in the // back up. The length of the array determines the number of times in the
@ -574,7 +583,8 @@ public class GameUtils {
} }
public static long makeRematch( Context context, long srcRowid, public static long makeRematch( Context context, long srcRowid,
long groupID, String gameName ) long groupID, String gameName,
NeedRematchOrder nro )
{ {
long rowid = DBUtils.ROWID_NOTFOUND; long rowid = DBUtils.ROWID_NOTFOUND;
try ( GameLock lock = GameLock.tryLockRO( srcRowid ) ) { try ( GameLock lock = GameLock.tryLockRO( srcRowid ) ) {
@ -582,10 +592,18 @@ public class GameUtils {
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = new CurGameInfo( context );
try ( GamePtr gamePtr = loadMakeGame( context, gi, lock ) ) { try ( GamePtr gamePtr = loadMakeGame( context, gi, lock ) ) {
if ( null != gamePtr ) { if ( null != gamePtr ) {
RematchOrder ro = RematchOrder.RO_SAME;
if ( XwJNI.server_canOfferRematch( gamePtr ) ) {
ro = XWPrefs.getDefaultRematchOrder( context );
if ( null == ro ) {
ro = nro.getRematchOrder();
}
}
if ( null != ro ) {
UtilCtxt util = new UtilCtxtImpl( context ); UtilCtxt util = new UtilCtxtImpl( context );
CommonPrefs cp = CommonPrefs.get(context); CommonPrefs cp = CommonPrefs.get( context );
try ( GamePtr gamePtrNew = XwJNI try ( GamePtr gamePtrNew = XwJNI
.game_makeRematch( gamePtr, util, cp, gameName ) ) { .game_makeRematch( gamePtr, util, cp, gameName, ro ) ) {
if ( null != gamePtrNew ) { if ( null != gamePtrNew ) {
rowid = saveNewGame1( context, gamePtrNew, rowid = saveNewGame1( context, gamePtrNew,
groupID, gameName ); groupID, gameName );
@ -595,6 +613,7 @@ public class GameUtils {
} }
} }
} }
}
Log.d( TAG, "makeRematch() => %d", rowid ); Log.d( TAG, "makeRematch() => %d", rowid );
return rowid; return rowid;

View file

@ -67,6 +67,7 @@ import org.eehouse.android.xw4.jni.CurGameInfo;
import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.jni.LastMoveInfo;
import org.eehouse.android.xw4.jni.XwJNI; import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
import static org.eehouse.android.xw4.DBUtils.ROWID_NOTFOUND; import static org.eehouse.android.xw4.DBUtils.ROWID_NOTFOUND;
@ -874,6 +875,12 @@ public class GamesListDelegate extends ListDelegateBase
} }
break; break;
case GAMES_LIST_GET_RO: {
NRO nro = (NRO)params[0];
dialog = mkRematchConfigDlg( nro );
}
break;
case GAMES_LIST_NAME_REMATCH: { case GAMES_LIST_NAME_REMATCH: {
final LinearLayout view = (LinearLayout) final LinearLayout view = (LinearLayout)
LocUtils.inflate( m_activity, R.layout.msg_label_and_edit ); LocUtils.inflate( m_activity, R.layout.msg_label_and_edit );
@ -1577,6 +1584,30 @@ public class GamesListDelegate extends ListDelegateBase
return handled || super.onDismissed( action, params ); return handled || super.onDismissed( action, params );
} }
private Dialog mkRematchConfigDlg( NRO nro )
{
final RematchConfigView view = (RematchConfigView)
LocUtils.inflate( m_activity, R.layout.rematch_config );
int iconResID = nro.isSolo()
? R.drawable.ic_sologame : R.drawable.ic_multigame;
AlertDialog.Builder ab = makeAlertBuilder()
.setView( view )
.setIcon( iconResID )
.setTitle( R.string.button_rematch )
.setNegativeButton( android.R.string.cancel, null )
.setPositiveButton( android.R.string.ok,
new OnClickListener() {
@Override
public void onClick( DialogInterface dlg, int ii ) {
RematchOrder ro = view.onOkClicked();
nro.rerun( ro );
}
} )
;
return ab.create();
}
private Dialog mkLoadStoreDlg( final Uri uri ) private Dialog mkLoadStoreDlg( final Uri uri )
{ {
final BackupConfigView view = (BackupConfigView) final BackupConfigView view = (BackupConfigView)
@ -2358,6 +2389,7 @@ public class GamesListDelegate extends ListDelegateBase
button.setVisibility( View.VISIBLE ); button.setVisibility( View.VISIBLE );
final boolean solo = isSolos[ii]; final boolean solo = isSolos[ii];
button.setOnClickListener( new View.OnClickListener() { button.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View view ) { public void onClick( View view ) {
curThis().handleNewGameButton( solo ); curThis().handleNewGameButton( solo );
} }
@ -2710,7 +2742,52 @@ public class GamesListDelegate extends ListDelegateBase
} }
} }
private class NRO implements Serializable, GameUtils.NeedRematchOrder {
private RematchOrder mChosenOrder = null;
private Bundle mExtras;
private String mGameName;
private CommsConnTypeSet mAddrs;
NRO( Bundle extras, String gameName, CommsConnTypeSet addrs )
{
mExtras = extras;
mGameName = gameName;
mAddrs = addrs;
}
@Override
public RematchOrder getRematchOrder()
{
RematchOrder result = mChosenOrder;
if ( null == result ) {
showDialogFragment( DlgID.GAMES_LIST_GET_RO, this );
}
return result;
}
boolean isSolo() { return mExtras.getBoolean( REMATCH_IS_SOLO, true ); }
void rerun( RematchOrder ro )
{
mChosenOrder = ro;
m_rematchExtras = mExtras;
runOnUiThread( new Runnable() {
@Override
public void run() {
rematchWithNameAndPerm( mGameName, mAddrs, NRO.this );
}
} );
}
} // class NRO
private void rematchWithNameAndPerm( String gameName, CommsConnTypeSet addrs ) private void rematchWithNameAndPerm( String gameName, CommsConnTypeSet addrs )
{
NRO nro = new NRO( m_rematchExtras, gameName, addrs );
rematchWithNameAndPerm( gameName, addrs, nro );
}
private void rematchWithNameAndPerm( String gameName, CommsConnTypeSet addrs,
NRO nro )
{ {
if ( null != gameName && 0 < gameName.length() ) { if ( null != gameName && 0 < gameName.length() ) {
Bundle extras = m_rematchExtras; Bundle extras = m_rematchExtras;
@ -2720,8 +2797,9 @@ public class GamesListDelegate extends ListDelegateBase
DBUtils.GROUPID_UNSPEC ); DBUtils.GROUPID_UNSPEC );
long newid = GameUtils.makeRematch( m_activity, srcRowID, long newid = GameUtils.makeRematch( m_activity, srcRowID,
groupID, gameName ); groupID, gameName, nro );
if ( DBUtils.ROWID_NOTFOUND != newid ) {
if ( extras.getBoolean( REMATCH_DELAFTER_EXTRA, false ) ) { if ( extras.getBoolean( REMATCH_DELAFTER_EXTRA, false ) ) {
String name = DBUtils.getName( m_activity, srcRowID ); String name = DBUtils.getName( m_activity, srcRowID );
makeConfirmThenBuilder( Action.LAUNCH_AFTER_DEL, makeConfirmThenBuilder( Action.LAUNCH_AFTER_DEL,
@ -2733,6 +2811,7 @@ public class GamesListDelegate extends ListDelegateBase
launchGame( newid ); launchGame( newid );
} }
} }
}
m_rematchExtras = null; m_rematchExtras = null;
} }

View file

@ -0,0 +1,78 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2020 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.
*/
package org.eehouse.android.xw4;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import java.util.HashMap;
import java.util.Map;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
import org.eehouse.android.xw4.loc.LocUtils;
public class RematchConfigView extends LinearLayout
{
private Context mContext;
private RadioGroup mGroup;
Map<Integer, RematchOrder> mRos = new HashMap<>();
public RematchConfigView( Context cx, AttributeSet as )
{
super( cx, as );
mContext = cx;
}
@Override
protected void onFinishInflate()
{
mGroup = (RadioGroup)findViewById( R.id.group );
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 ( 1 == mRos.size() ) {
button.setChecked( true );
}
}
}
public RematchOrder onOkClicked()
{
int id = mGroup.getCheckedRadioButtonId();
RematchOrder ro = mRos.get(id);
// Save it if default button checked
CheckBox check = (CheckBox)findViewById( R.id.make_default );
if ( check.isChecked() ) {
XWPrefs.setDefaultRematchOrder( mContext, ro );
}
return ro;
}
}

View file

@ -32,6 +32,8 @@ import org.json.JSONObject;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
public class XWPrefs { public class XWPrefs {
private static final String TAG = XWPrefs.class.getSimpleName(); private static final String TAG = XWPrefs.class.getSimpleName();
@ -328,6 +330,30 @@ public class XWPrefs {
return groupID; return groupID;
} }
public static void setDefaultRematchOrder( Context context, RematchOrder ro )
{
String storedStr = null == ro ? "" : context.getString( ro.getStrID() );
setPrefsString( context, R.string.key_rematch_order, storedStr );
}
public static RematchOrder getDefaultRematchOrder( Context context )
{
String storedStr = getPrefsString( context, R.string.key_rematch_order );
// Let's try to get this from the enum...
RematchOrder ro = null;
for ( RematchOrder one: RematchOrder.values() ) {
int strID = one.getStrID();
String str = context.getString( strID );
if ( str.equals( storedStr ) ) {
ro = one;
break;
}
}
return ro;
}
public static void setDefaultNewGameGroup( Context context, long val ) public static void setDefaultNewGameGroup( Context context, long val )
{ {
Assert.assertTrue( DBUtils.GROUPID_UNSPEC != val ); Assert.assertTrue( DBUtils.GROUPID_UNSPEC != val );

View file

@ -31,6 +31,7 @@ import org.eehouse.android.xw4.DbgUtils;
import org.eehouse.android.xw4.Log; import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.NetLaunchInfo; import org.eehouse.android.xw4.NetLaunchInfo;
import org.eehouse.android.xw4.Quarantine; import org.eehouse.android.xw4.Quarantine;
import org.eehouse.android.xw4.R;
import org.eehouse.android.xw4.Utils.ISOCode; import org.eehouse.android.xw4.Utils.ISOCode;
import org.eehouse.android.xw4.Utils; import org.eehouse.android.xw4.Utils;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -336,11 +337,24 @@ public class XwJNI {
return gamePtr; return gamePtr;
} }
// Keep in sync with server.h
public enum RematchOrder {
RO_SAME(R.string.ro_same),
RO_LOW_SCORE_FIRST(R.string.ro_low_score_first),
RO_HIGH_SCORE_FIRST(R.string.ro_high_score_first),
RO_JUGGLE(R.string.ro_juggle),
;
private int mStrID;
private RematchOrder(int str) { mStrID = str; }
public int getStrID() { return mStrID; }
};
public static GamePtr game_makeRematch( GamePtr gamePtr, UtilCtxt util, public static GamePtr game_makeRematch( GamePtr gamePtr, UtilCtxt util,
CommonPrefs cp, String gameName ) CommonPrefs cp, String gameName,
RematchOrder ro )
{ {
GamePtr gamePtrNew = initGameJNI( 0 ); GamePtr gamePtrNew = initGameJNI( 0 );
if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName ) ) { if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName, ro ) ) {
gamePtrNew.release(); gamePtrNew.release();
gamePtrNew = null; gamePtrNew = null;
} }
@ -384,7 +398,7 @@ public class XwJNI {
private static native boolean game_makeRematch( GamePtr gamePtr, private static native boolean game_makeRematch( GamePtr gamePtr,
GamePtr gamePtrNew, GamePtr gamePtrNew,
UtilCtxt util, CommonPrefs cp, UtilCtxt util, CommonPrefs cp,
String gameName ); String gameName, RematchOrder ro );
private static native boolean game_makeFromInvite( GamePtr gamePtr, NetLaunchInfo nli, private static native boolean game_makeFromInvite( GamePtr gamePtr, NetLaunchInfo nli,
UtilCtxt util, UtilCtxt util,
@ -536,6 +550,7 @@ public class XwJNI {
public static native boolean server_getGameIsConnected( GamePtr gamePtr ); public static native boolean server_getGameIsConnected( GamePtr gamePtr );
public static native String server_writeFinalScores( GamePtr gamePtr ); public static native String server_writeFinalScores( GamePtr gamePtr );
public static native boolean server_initClientConnection( GamePtr gamePtr ); public static native boolean server_initClientConnection( GamePtr gamePtr );
public static native boolean server_canOfferRematch( GamePtr gamePtr );
public static native void server_endGame( GamePtr gamePtr ); public static native void server_endGame( GamePtr gamePtr );
// hybrid to save work // hybrid to save work

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<org.eehouse.android.xw4.RematchConfigView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
>
<TextView android:id="@+id/explanation"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/expl_rematch_order"
/>
<RadioGroup android:id="@+id/group"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<CheckBox android:id="@+id/make_default"
android:text="@string/dicts_item_select"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="12dp"
/>
</org.eehouse.android.xw4.RematchConfigView>

View file

@ -83,6 +83,7 @@
<string name="key_robot_name">key_robot_name</string> <string name="key_robot_name">key_robot_name</string>
<string name="key_default_robodict">key_default_robodict</string> <string name="key_default_robodict">key_default_robodict</string>
<string name="key_default_phonies">key_default_phonies2</string> <string name="key_default_phonies">key_default_phonies2</string>
<string name="key_rematch_order">key_rematch_order</string>
<string name="key_default_timerenabled">key_default_timerenabled</string> <string name="key_default_timerenabled">key_default_timerenabled</string>
<string name="key_notify_sound">key_notify_sound</string> <string name="key_notify_sound">key_notify_sound</string>
<string name="key_disable_mqtt">key_disable_mqtt</string> <string name="key_disable_mqtt">key_disable_mqtt</string>
@ -222,6 +223,14 @@
<item>@string/phonies_block</item> <item>@string/phonies_block</item>
</string-array> </string-array>
<string-array name="ro_names">
<item>@string/ro_no_default</item>
<item>@string/ro_same</item>
<item>@string/ro_low_score_first</item>
<item>@string/ro_high_score_first</item>
<item>@string/ro_juggle</item>
</string-array>
<string-array name="robot_levels"> <string-array name="robot_levels">
<item>@string/robot_smartest</item> <item>@string/robot_smartest</item>
<item>@string/robot_smarter</item> <item>@string/robot_smarter</item>

View file

@ -5,4 +5,12 @@
<string name="dup_allscores_fmt">All scores: %1$s</string> <string name="dup_allscores_fmt">All scores: %1$s</string>
<string name="expl_rematch_order">Choose how to order players in the new game</string>
<string name="title_rematch_order">Rematched Players Order</string>
<string name="ro_no_default">Ask each time</string>
<string name="ro_same">Same as this game</string>
<string name="ro_low_score_first">Low scorer goes first</string>
<string name="ro_high_score_first">High scorer goes first</string>
<string name="ro_juggle">Randomly</string>
</resources> </resources>

View file

@ -16,6 +16,13 @@
android:title="@string/title_addrs_pref" android:title="@string/title_addrs_pref"
/> />
<org.eehouse.android.xw4.XWListPreference
android:key="@string/key_rematch_order"
android:title="@string/title_rematch_order"
android:entries="@array/ro_names"
android:entryValues="@array/ro_names"
/>
<Preference app:title="@string/prefs_dicts" <Preference app:title="@string/prefs_dicts"
app:summary="@string/prefs_dicts_summary" app:summary="@string/prefs_dicts_summary"
app:fragment="org.eehouse.android.xw4.gen.PrefsWrappers$prefs_dflts_dicts" app:fragment="org.eehouse.android.xw4.gen.PrefsWrappers$prefs_dflts_dicts"

View file

@ -1423,7 +1423,7 @@ initGameGlobals( JNIEnv* env, JNIState* state, jobject jutil, jobject jprocs )
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch
( JNIEnv* env, jclass C, GamePtrType gamePtr, GamePtrType gamePtrNew, ( JNIEnv* env, jclass C, GamePtrType gamePtr, GamePtrType gamePtrNew,
jobject jutil, jobject jcp, jstring jGameName ) jobject jutil, jobject jcp, jstring jGameName, jobject jRo )
{ {
jboolean success = false; jboolean success = false;
XWJNI_START_GLOBALS(gamePtr); XWJNI_START_GLOBALS(gamePtr);
@ -1437,8 +1437,10 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch
loadCommonPrefs( env, &cp, jcp ); loadCommonPrefs( env, &cp, jcp );
const char* gameName = (*env)->GetStringUTFChars( env, jGameName, NULL ); const char* gameName = (*env)->GetStringUTFChars( env, jGameName, NULL );
RematchOrder ro = jEnumToInt( env, jRo );
success = game_makeRematch( &oldState->game, env, globals->util, &cp, success = game_makeRematch( &oldState->game, env, globals->util, &cp,
(TransportProcs*)NULL, &state->game, gameName ); (TransportProcs*)NULL, &state->game,
gameName, ro );
(*env)->ReleaseStringUTFChars( env, jGameName, gameName ); (*env)->ReleaseStringUTFChars( env, jGameName, gameName );
if ( success ) { if ( success ) {
@ -2200,6 +2202,19 @@ Java_org_eehouse_android_xw4_jni_XwJNI_server_1initClientConnection
return result; return result;
} }
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_server_1canOfferRematch
( JNIEnv* env, jclass C, GamePtrType gamePtr )
{
jboolean result;
XWJNI_START_GLOBALS(gamePtr);
XP_Bool canOffer;
XP_Bool canRematch= server_canRematch( state->game.server, &canOffer );
result = canRematch && canOffer;
XWJNI_END();
return result;
}
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1start Java_org_eehouse_android_xw4_jni_XwJNI_comms_1start
( JNIEnv* env, jclass C, GamePtrType gamePtr ) ( JNIEnv* env, jclass C, GamePtrType gamePtr )

View file

@ -239,6 +239,7 @@ struct CommsCtxt {
#define FLAG_HARVEST_DONE 1 #define FLAG_HARVEST_DONE 1
#define FLAG_QUASHED 2 #define FLAG_QUASHED 2
#define QUASHED(COMMS) (0 != ((COMMS)->flags & FLAG_QUASHED)) #define QUASHED(COMMS) (0 != ((COMMS)->flags & FLAG_QUASHED))
#if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_DIRECTIP #if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_DIRECTIP
@ -655,7 +656,7 @@ comms_setConnID( CommsCtxt* comms, XP_U32 connID, XP_U16 streamVersion )
XP_ASSERT( 0 == comms->streamVersion XP_ASSERT( 0 == comms->streamVersion
|| streamVersion == comms->streamVersion ); || streamVersion == comms->streamVersion );
comms->streamVersion = streamVersion; comms->streamVersion = streamVersion;
XP_LOGFF( "set connID (gameID) to %x, streamVersion to 0x%X", XP_LOGFF( "set connID (gameID) to %X, streamVersion to 0x%X",
connID, streamVersion ); connID, streamVersion );
THREAD_CHECK_END(); THREAD_CHECK_END();
} /* comms_setConnID */ } /* comms_setConnID */
@ -724,6 +725,7 @@ addrFromStreamOne( CommsAddrRec* addrP, XWStreamCtxt* stream, CommsConnType typ
void void
addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream ) addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream )
{ {
XP_MEMSET( addrP, 0, sizeof(*addrP) );
XP_U8 tmp = stream_getU8( stream ); XP_U8 tmp = stream_getU8( stream );
XP_U16 version = stream_getVersion( stream ); XP_U16 version = stream_getVersion( stream );
XP_ASSERT( 0 < version ); XP_ASSERT( 0 < version );
@ -1381,6 +1383,41 @@ comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
XP_ASSERT( found ); XP_ASSERT( found );
} }
static XP_Bool
addrs_same( const CommsAddrRec* addr1, const CommsAddrRec* addr2 )
{
/* Empty addresses are the same only if both are empty */
XP_Bool same = addr1->_conTypes == 0 && addr2->_conTypes == 0;
CommsConnType typ;
for ( XP_U32 st = 0; !same && addr_iter( addr1, &typ, &st ); ) {
if ( addr_hasType( addr2, typ ) ) {
switch ( typ ) {
case COMMS_CONN_MQTT:
same = addr1->u.mqtt.devID == addr2->u.mqtt.devID;
break;
case COMMS_CONN_SMS:
same = addr1->u.sms.port == addr2->u.sms.port
&& 0 == XP_STRCMP(addr1->u.sms.phone, addr2->u.sms.phone );
break;
default:
XP_LOGFF( "ignoring %s", ConnType2Str(typ) );
}
}
}
return same;
}
XP_Bool
comms_addrsAreSame( const CommsCtxt* XP_UNUSED(comms),
const CommsAddrRec* addr1,
const CommsAddrRec* addr2 )
{
XP_Bool result = addrs_same( addr1, addr2 );
return result;
}
typedef struct _NonAcks { typedef struct _NonAcks {
int count; int count;
} NonAcks; } NonAcks;
@ -3604,16 +3641,16 @@ static void
logAddrComms( const CommsCtxt* comms, const CommsAddrRec* addr, logAddrComms( const CommsCtxt* comms, const CommsAddrRec* addr,
const char* caller ) const char* caller )
{ {
logAddr( MPPARM(comms->mpool) comms->dutil, addr, caller ); logAddr( comms->dutil, addr, caller );
} }
void void
logAddr( MPFORMAL XW_DUtilCtxt* dutil, const CommsAddrRec* addr, logAddr( XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
const char* caller ) const char* caller )
{ {
if ( !!addr ) { if ( !!addr ) {
char buf[128]; char buf[128];
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(dutil->mpool)
dutil_getVTManager(dutil)); dutil_getVTManager(dutil));
if ( !!caller ) { if ( !!caller ) {
snprintf( buf, sizeof(buf), "called on %p from %s:\n", snprintf( buf, sizeof(buf), "called on %p from %s:\n",
@ -3872,6 +3909,14 @@ types_hasType( XP_U16 conTypes, CommsConnType typ )
return hasType; return hasType;
} }
XP_Bool
addr_isEmpty( const CommsAddrRec* addr )
{
CommsConnType typ;
XP_U32 st = 0;
return !addr_iter( addr, &typ, &st );
}
CommsConnType CommsConnType
addr_getType( const CommsAddrRec* addr ) addr_getType( const CommsAddrRec* addr )
{ {

View file

@ -219,6 +219,8 @@ XP_S16 comms_resendAll( CommsCtxt* comms, XWEnv xwe, CommsConnType filter,
XP_U16 comms_getChannelSeed( CommsCtxt* comms ); XP_U16 comms_getChannelSeed( CommsCtxt* comms );
void comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo, void comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
CommsAddrRec* addr ); CommsAddrRec* addr );
XP_Bool comms_addrsAreSame( const CommsCtxt* comms, const CommsAddrRec* addr1,
const CommsAddrRec* addr2 );
#ifdef XWFEATURE_COMMSACK #ifdef XWFEATURE_COMMSACK
void comms_ackAny( CommsCtxt* comms, XWEnv xwe ); void comms_ackAny( CommsCtxt* comms, XWEnv xwe );
@ -256,6 +258,7 @@ void comms_gameJoined( CommsCtxt* comms, const XP_UCHAR* connname, XWHostID hid
XP_Bool augmentAddr( CommsAddrRec* addr, const CommsAddrRec* newer, XP_Bool augmentAddr( CommsAddrRec* addr, const CommsAddrRec* newer,
XP_Bool isNewer ); XP_Bool isNewer );
XP_Bool addr_isEmpty( const CommsAddrRec* addr );
CommsConnType addr_getType( const CommsAddrRec* addr ); CommsConnType addr_getType( const CommsAddrRec* addr );
void addr_setType( CommsAddrRec* addr, CommsConnType type ); void addr_setType( CommsAddrRec* addr, CommsConnType type );
void addr_addType( CommsAddrRec* addr, CommsConnType type ); void addr_addType( CommsAddrRec* addr, CommsConnType type );
@ -283,7 +286,7 @@ void comms_setAddrDisabled( CommsCtxt* comms, CommsConnType typ,
XP_Bool send, XP_Bool enabled ); XP_Bool send, XP_Bool enabled );
XP_Bool comms_getAddrDisabled( const CommsCtxt* comms, CommsConnType typ, XP_Bool comms_getAddrDisabled( const CommsCtxt* comms, CommsConnType typ,
XP_Bool send ); XP_Bool send );
void logAddr( MPFORMAL XW_DUtilCtxt* dutil, const CommsAddrRec* addr, void logAddr( XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
const char* caller ); const char* caller );
# else # else

View file

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

View file

@ -237,14 +237,14 @@ game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
XP_Bool XP_Bool
game_makeRematch( const XWGame* oldGame, XWEnv xwe, XW_UtilCtxt* newUtil, game_makeRematch( const XWGame* oldGame, XWEnv xwe, XW_UtilCtxt* newUtil,
const CommonPrefs* newCp, const TransportProcs* procs, const CommonPrefs* newCp, const TransportProcs* procs,
XWGame* newGame, const XP_UCHAR* newName ) XWGame* newGame, const XP_UCHAR* newName, RematchOrder ro )
{ {
XP_Bool success = XP_FALSE; XP_Bool success = XP_FALSE;
XP_LOGFF( "(newName=%s)", newName ); XP_LOGFF( "(newName=%s, ro=%s)", newName, RO2Str(ro) );
RematchAddrs ra; RematchInfo* rip;
if ( server_getRematchInfo( oldGame->server, newUtil, if ( server_getRematchInfo( oldGame->server, newUtil,
makeGameID( newUtil ), &ra ) ) { makeGameID( newUtil ), ro, &rip ) ) {
CommsAddrRec* selfAddrP = NULL; CommsAddrRec* selfAddrP = NULL;
CommsAddrRec selfAddr; CommsAddrRec selfAddr;
if ( !!oldGame->comms ) { if ( !!oldGame->comms ) {
@ -255,20 +255,31 @@ game_makeRematch( const XWGame* oldGame, XWEnv xwe, XW_UtilCtxt* newUtil,
if ( game_makeNewGame( MPPARM(newUtil->mpool) xwe, newGame, if ( game_makeNewGame( MPPARM(newUtil->mpool) xwe, newGame,
newUtil->gameInfo, selfAddrP, (CommsAddrRec*)NULL, newUtil->gameInfo, selfAddrP, (CommsAddrRec*)NULL,
newUtil, (DrawCtx*)NULL, newCp, procs ) ) { newUtil, (DrawCtx*)NULL, newCp, procs ) ) {
if ( !!newGame->comms ) {
server_setRematchOrder( newGame->server, rip );
const CurGameInfo* newGI = newUtil->gameInfo; const CurGameInfo* newGI = newUtil->gameInfo;
for ( int ii = 0; ii < ra.nAddrs; ++ii ) { 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; NetLaunchInfo nli;
/* hard-code one player per device -- for now */ nli_init( &nli, newGI, selfAddrP, nPlayersH, ii + 1 );
nli_init( &nli, newGI, selfAddrP, 1, ii + 1 );
if ( !!newName ) { if ( !!newName ) {
nli_setGameName( &nli, newName ); nli_setGameName( &nli, newName );
} }
LOGNLI( &nli ); LOGNLI( &nli );
comms_invite( newGame->comms, xwe, &nli, &ra.addrs[ii], XP_TRUE ); comms_invite( newGame->comms, xwe, &nli, &guestAddr, XP_TRUE );
}
} }
success = XP_TRUE; success = XP_TRUE;
} }
server_disposeRematchInfo( oldGame->server, &rip );
} }
LOG_RETURNF( "%s", boolToStr(success) ); LOG_RETURNF( "%s", boolToStr(success) );
return success; return success;
@ -518,8 +529,7 @@ game_getState( const XWGame* game, XWEnv xwe, GameStateInfo* gsi )
gsi->canTrade = board_canTrade( board, xwe ); gsi->canTrade = board_canTrade( board, xwe );
gsi->nPendingMessages = !!game->comms ? gsi->nPendingMessages = !!game->comms ?
comms_countPendingPackets(game->comms, NULL) : 0; comms_countPendingPackets(game->comms, NULL) : 0;
gsi->canRematch = server_canRematch( server, NULL );
gsi->canRematch = server_canRematch( server );
gsi->canPause = server_canPause( server ); gsi->canPause = server_canPause( server );
gsi->canUnpause = server_canUnpause( server ); gsi->canUnpause = server_canUnpause( server );
} }

View file

@ -86,9 +86,11 @@ XP_Bool game_makeNewGame( MPFORMAL XWEnv xwe, XWGame* game, CurGameInfo* gi,
,XP_U16 gameSeed ,XP_U16 gameSeed
#endif #endif
); );
XP_Bool game_makeRematch( const XWGame* game, XWEnv xwe, XW_UtilCtxt* util, XP_Bool game_makeRematch( const XWGame* game, XWEnv xwe, XW_UtilCtxt* util,
const CommonPrefs* cp, const TransportProcs* procs, const CommonPrefs* cp, const TransportProcs* procs,
XWGame* newGame, const XP_UCHAR* newName ); XWGame* newGame, const XP_UCHAR* newName,
RematchOrder ro );
void game_changeDict( MPFORMAL XWGame* game, XWEnv xwe, CurGameInfo* gi, void game_changeDict( MPFORMAL XWGame* game, XWEnv xwe, CurGameInfo* gi,
DictionaryCtxt* dict ); DictionaryCtxt* dict );

View file

@ -275,7 +275,7 @@ typedef struct WordNotifierInfo {
XP_Bool getCurrentMoveScoreIfLegal( ModelCtxt* model, XWEnv xwe, XP_Bool getCurrentMoveScoreIfLegal( ModelCtxt* model, XWEnv xwe,
XP_S16 turn, XWStreamCtxt* stream, XP_S16 turn, XWStreamCtxt* stream,
WordNotifierInfo* wni, XP_S16* score ); WordNotifierInfo* wni, XP_S16* score );
XP_S16 model_getPlayerScore( ModelCtxt* model, XP_S16 player ); XP_S16 model_getPlayerScore( const ModelCtxt* model, XP_S16 player );
XP_Bool model_getPlayersLastScore( ModelCtxt* model, XWEnv xwe, XP_S16 player, XP_Bool model_getPlayersLastScore( ModelCtxt* model, XWEnv xwe, XP_S16 player,
LastMoveInfo* info ); LastMoveInfo* info );
@ -299,9 +299,12 @@ XP_Bool model_checkMoveLegal( ModelCtxt* model, XWEnv xwe, XP_S16 player,
WordNotifierInfo* notifyInfo ); WordNotifierInfo* notifyInfo );
typedef struct _ScoresArray { XP_S16 arr[MAX_NUM_PLAYERS]; } ScoresArray; typedef struct _ScoresArray { XP_S16 arr[MAX_NUM_PLAYERS]; } ScoresArray;
void model_figureFinalScores( ModelCtxt* model, ScoresArray* scores, void model_figureFinalScores( const ModelCtxt* model, ScoresArray* scores,
ScoresArray* tilePenalties ); ScoresArray* tilePenalties );
void model_getCurScores( const ModelCtxt* model, ScoresArray* scores,
XP_Bool gameOver );
/* figureMoveScore is meant only for the engine's use */ /* figureMoveScore is meant only for the engine's use */
XP_U16 figureMoveScore( const ModelCtxt* model, XWEnv xwe, XP_U16 turn, XP_U16 figureMoveScore( const ModelCtxt* model, XWEnv xwe, XP_U16 turn,
const MoveInfo* mvInfo, EngineCtxt* engine, const MoveInfo* mvInfo, EngineCtxt* engine,

View file

@ -145,7 +145,7 @@ getCurrentMoveScoreIfLegal( ModelCtxt* model, XWEnv xwe, XP_S16 turn,
} /* getCurrentMoveScoreIfLegal */ } /* getCurrentMoveScoreIfLegal */
XP_S16 XP_S16
model_getPlayerScore( ModelCtxt* model, XP_S16 player ) model_getPlayerScore( const ModelCtxt* model, XP_S16 player )
{ {
return model->players[player].score; return model->players[player].score;
} /* model_getPlayerScore */ } /* model_getPlayerScore */
@ -155,7 +155,7 @@ model_getPlayerScore( ModelCtxt* model, XP_S16 player )
* player. * player.
*/ */
void void
model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP, model_figureFinalScores( const ModelCtxt* model, ScoresArray* finalScoresP,
ScoresArray* tilePenaltiesP ) ScoresArray* tilePenaltiesP )
{ {
XP_S16 ii, jj; XP_S16 ii, jj;
@ -164,7 +164,7 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
XP_U16 nPlayers = model->nPlayers; XP_U16 nPlayers = model->nPlayers;
XP_S16 firstDoneIndex = -1; /* not set unless FIRST_DONE_BONUS is set */ XP_S16 firstDoneIndex = -1; /* not set unless FIRST_DONE_BONUS is set */
const TrayTileSet* tray; const TrayTileSet* tray;
PlayerCtxt* player; const PlayerCtxt* player;
const DictionaryCtxt* dict = model_getDictionary( model ); const DictionaryCtxt* dict = model_getDictionary( model );
CurGameInfo* gi = model->vol.gi; CurGameInfo* gi = model->vol.gi;
@ -217,6 +217,20 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
} }
} /* model_figureFinalScores */ } /* model_figureFinalScores */
void
model_getCurScores( const ModelCtxt* model, ScoresArray* scores,
XP_Bool gameOver )
{
if ( gameOver ) {
model_figureFinalScores( model, scores, NULL );
} else {
int nPlayers = model->vol.gi->nPlayers;
for ( int ii = 0; ii < nPlayers; ++ii ) {
scores->arr[ii] = model_getPlayerScore( model, ii );
}
}
}
typedef struct _BlockCheckState { typedef struct _BlockCheckState {
ModelCtxt* model; ModelCtxt* model;
XWStreamCtxt* stream; XWStreamCtxt* stream;

View file

@ -33,8 +33,8 @@ typedef enum {OSType_NONE, OSType_LINUX, OSType_ANDROID, } XP_OSType;
typedef struct _NetLaunchInfo { typedef struct _NetLaunchInfo {
XP_U16 _conTypes; XP_U16 _conTypes;
XP_UCHAR gameName[MAX_GAME_NAME_LEN]; XP_UCHAR gameName[MAX_GAME_NAME_LEN+1];
XP_UCHAR dict[MAX_DICT_NAME_LEN]; XP_UCHAR dict[MAX_DICT_NAME_LEN+1];
XP_UCHAR isoCodeStr[MAX_ISO_CODE_LEN+1]; XP_UCHAR isoCodeStr[MAX_ISO_CODE_LEN+1];
XP_U8 forceChannel; XP_U8 forceChannel;
XP_U8 nPlayersT; XP_U8 nPlayersT;
@ -42,6 +42,9 @@ typedef struct _NetLaunchInfo {
XP_Bool remotesAreRobots; XP_Bool remotesAreRobots;
XP_Bool inDuplicateMode; XP_Bool inDuplicateMode;
XP_U32 gameID;
XP_UCHAR inviteID[32]; /* still used? */
/* Relay */ /* Relay */
XP_UCHAR room[MAX_INVITE_LEN + 1]; XP_UCHAR room[MAX_INVITE_LEN + 1];
XP_U32 devID; /* not used on android; remove?? */ XP_U32 devID; /* not used on android; remove?? */
@ -53,12 +56,9 @@ typedef struct _NetLaunchInfo {
// SMS // SMS
XP_UCHAR phone[32]; XP_UCHAR phone[32];
XP_Bool isGSM; XP_Bool isGSM;
XP_OSType osType; XP_OSType osType; /* used? */
XP_U32 osVers; XP_U32 osVers;
XP_U32 gameID;
XP_UCHAR inviteID[32];
/* MQTT */ /* MQTT */
XP_UCHAR mqttDevID[17]; XP_UCHAR mqttDevID[17];
} NetLaunchInfo; } NetLaunchInfo;

View file

@ -200,16 +200,10 @@ drawScoreBoard( BoardCtxt* board, XWEnv xwe )
#endif #endif
/* Get the scores from the model or by calculating them based on /* Get the scores from the model or by calculating them based on
the end-of-game state. */ the end-of-game state. */
if ( board->gameOver ) { model_getCurScores( model, &scores, board->gameOver );
model_figureFinalScores( model, &scores, NULL );
} else {
for ( ii = 0; ii < nPlayers; ++ii ) {
scores.arr[ii] = model_getPlayerScore( model, ii );
}
}
if ( draw_scoreBegin( board->draw, xwe, &board->scoreBdBounds, nPlayers, if ( draw_scoreBegin( board->draw, xwe, &board->scoreBdBounds,
scores.arr, nTilesInPool, nPlayers, scores.arr, nTilesInPool,
dfsFor( board, OBJ_SCORE ) ) ) { dfsFor( board, OBJ_SCORE ) ) ) {
XP_U16 totalDim = 0; /* not counting rem */ XP_U16 totalDim = 0; /* not counting rem */
XP_U16 gotPct; XP_U16 gotPct;

File diff suppressed because it is too large Load diff

View file

@ -88,7 +88,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_dupTurnDone( const ServerCtxt* server, XP_U16 turn );
XP_Bool server_canPause( const ServerCtxt* server ); XP_Bool server_canPause( const ServerCtxt* server );
XP_Bool server_canUnpause( const ServerCtxt* server ); XP_Bool server_canUnpause( const ServerCtxt* server );
XP_Bool server_canRematch( const ServerCtxt* server ); XP_Bool server_canRematch( const ServerCtxt* server, XP_Bool* canOrder );
void server_pause( ServerCtxt* server, XWEnv xwe, XP_S16 turn, const XP_UCHAR* msg ); 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 ); void server_unpause( ServerCtxt* server, XWEnv xwe, XP_S16 turn, const XP_UCHAR* msg );
@ -146,18 +146,46 @@ XP_U16 server_figureFinishBonus( const ServerCtxt* server, XP_U16 turn );
XP_Bool server_getIsHost( const ServerCtxt* server ); XP_Bool server_getIsHost( const ServerCtxt* server );
#endif #endif
typedef struct _RematchAddrs { typedef enum {
CommsAddrRec addrs[MAX_NUM_PLAYERS]; RO_SAME, /* preserve the parent game's order */
XP_U16 nAddrs; RO_LOW_SCORE_FIRST, /* lowest scorer in parent goes first, etc */
} RematchAddrs; RO_HIGH_SCORE_FIRST, /* highest scorer in parent goes first, etc */
RO_JUGGLE, /* rearrange randomly */
#ifdef XWFEATURE_RO_BYNAME
RO_BY_NAME, /* alphabetical -- for testing only! :-) */
#endif
RO_NUM_ROS,
} RematchOrder;
#ifdef DEBUG
const XP_UCHAR* RO2Str(RematchOrder ro);
#endif
/* Info about remote addresses that lets us determine an order for invited
players as they arrive. It stores the addresses of all remote devices, and
for each a mask of which players will come from that address.
No need for a count: once we find a playersMask == 0 we're done
*/
/* Sets up newUtil->gameInfo correctly, and returns with a set of /* Sets up newUtil->gameInfo correctly, and returns with a set of
addresses to which invitation should be sent. But: meant to be called addresses to which invitation should be sent. But: meant to be called
only from game.c anyway. only from game.c anyway.
*/ */
typedef struct RematchInfo RematchInfo;
XP_Bool server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil, XP_Bool server_getRematchInfo( const ServerCtxt* server, XW_UtilCtxt* newUtil,
XP_U32 gameID, RematchAddrs* ra ); XP_U32 gameID, RematchOrder ro, 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 );
/* Pass in the info the server will need to hang onto until all invitees have
registered, at which point it can set and communicate the player order for
the game. To be called only from game.c! */
void server_setRematchOrder( ServerCtxt* server, const RematchInfo* ri );
XP_Bool server_isFromRematch( const ServerCtxt* server );
#ifdef CPLUS #ifdef CPLUS
} }

View file

@ -518,6 +518,17 @@ randIntArray( XP_U16* rnums, XP_U16 count )
return changed; return changed;
} /* randIntArray */ } /* randIntArray */
XP_U16
countBits( XP_U32 mask )
{
XP_U16 result = 0;
while ( 0 != mask ) {
++result;
mask &= mask - 1;
}
return result;
}
#ifdef XWFEATURE_BASE64 #ifdef XWFEATURE_BASE64
/* base-64 encode binary data as a message legal for SMS. See /* base-64 encode binary data as a message legal for SMS. See
* http://www.ietf.org/rfc/rfc2045.txt for the algorithm. glib uses this and * http://www.ietf.org/rfc/rfc2045.txt for the algorithm. glib uses this and

View file

@ -122,6 +122,8 @@ XP_UCHAR* emptyStringIfNull( XP_UCHAR* str );
/* Produce an array of ints 0..count-1, juggled */ /* Produce an array of ints 0..count-1, juggled */
XP_Bool randIntArray( XP_U16* rnums, XP_U16 count ); XP_Bool randIntArray( XP_U16* rnums, XP_U16 count );
XP_U16 countBits( XP_U32 mask );
#ifdef XWFEATURE_BASE64 #ifdef XWFEATURE_BASE64
void binToSms( XP_UCHAR* out, XP_U16* outlen, const XP_U8* in, XP_U16 inlen ); void binToSms( XP_UCHAR* out, XP_U16* outlen, const XP_U8* in, XP_U16 inlen );
XP_Bool smsToBin( XP_U8* out, XP_U16* outlen, const XP_UCHAR* in, XP_U16 inlen ); XP_Bool smsToBin( XP_U8* out, XP_U16* outlen, const XP_UCHAR* in, XP_U16 inlen );

View file

@ -186,17 +186,6 @@ adjustCurSel( CursGameList* cgl )
cgl_draw( cgl ); cgl_draw( cgl );
} }
static int
countBits( int bits )
{
int result = 0;
while ( 0 != bits ) {
++result;
bits &= bits - 1;
}
return result;
}
void void
cgl_draw( CursGameList* cgl ) cgl_draw( CursGameList* cgl )
{ {

View file

@ -135,7 +135,8 @@ static void relay_requestJoin_curses( void* closure, const XP_UCHAR* devID,
XP_U16 nPlayersTotal, XP_U16 seed, XP_U16 lang ); XP_U16 nPlayersTotal, XP_U16 seed, XP_U16 lang );
#endif #endif
static XP_Bool rematch_and_save( CursesBoardGlobals* bGlobals, XP_U32* newGameIDP ); static XP_Bool rematch_and_save( CursesBoardGlobals* bGlobals, RematchOrder ro,
XP_U32* newGameIDP );
static void disposeBoard( CursesBoardGlobals* bGlobals ); static void disposeBoard( CursesBoardGlobals* bGlobals );
static void initCP( CommonGlobals* cGlobals ); static void initCP( CommonGlobals* cGlobals );
static CursesBoardGlobals* commonInit( CursesBoardState* cbState, static CursesBoardGlobals* commonInit( CursesBoardState* cbState,
@ -697,16 +698,17 @@ cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel,
NetLaunchInfo nli; NetLaunchInfo nli;
nli_init( &nli, cGlobals->gi, &selfAddr, 1, forceChannel ); nli_init( &nli, cGlobals->gi, &selfAddr, 1, forceChannel );
nli.remotesAreRobots = XP_TRUE;
comms_invite( comms, NULL_XWE, &nli, destAddr, XP_TRUE ); comms_invite( comms, NULL_XWE, &nli, destAddr, XP_TRUE );
} }
XP_Bool XP_Bool
cb_makeRematch( CursesBoardState* cbState, XP_U32 gameID, XP_U32* newGameIDP ) cb_makeRematch( CursesBoardState* cbState, XP_U32 gameID, RematchOrder ro,
XP_U32* newGameIDP )
{ {
CursesBoardGlobals* bGlobals = findOrOpenForGameID( cbState, gameID, NULL, NULL ); CursesBoardGlobals* bGlobals = findOrOpenForGameID( cbState, gameID,
XP_Bool success = rematch_and_save( bGlobals, newGameIDP ); NULL, NULL );
XP_Bool success = rematch_and_save( bGlobals, ro, newGameIDP );
return success; return success;
} }
@ -727,9 +729,11 @@ cb_makeMoveIf( CursesBoardState* cbState, XP_U32 gameID )
XP_FALSE, XP_FALSE,
#endif #endif
XP_FALSE, &ignored ); XP_FALSE, &ignored );
if ( success ) { if ( !success ) {
success = board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE, NULL ); XP_LOGFF( "unable to find hint; so PASSing" );
} }
success = board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE,
NULL );
} }
} }
LOG_RETURNF( "%s", boolToStr(success) ); LOG_RETURNF( "%s", boolToStr(success) );
@ -994,7 +998,7 @@ curses_util_informUndo( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe) )
} }
static void static void
rematch_and_save_once( CursesBoardGlobals* bGlobals ) rematch_and_save_once( CursesBoardGlobals* bGlobals, RematchOrder ro )
{ {
LOG_FUNC(); LOG_FUNC();
CommonGlobals* cGlobals = &bGlobals->cGlobals; CommonGlobals* cGlobals = &bGlobals->cGlobals;
@ -1006,7 +1010,7 @@ rematch_and_save_once( CursesBoardGlobals* bGlobals )
&& 0 != alreadyDone ) { && 0 != alreadyDone ) {
XP_LOGFF( "already rematched game %X", cGlobals->gi->gameID ); XP_LOGFF( "already rematched game %X", cGlobals->gi->gameID );
} else { } else {
if ( rematch_and_save( bGlobals, NULL ) ) { if ( rematch_and_save( bGlobals, ro, NULL ) ) {
gdb_storeInt( cGlobals->params->pDb, key, 1 ); gdb_storeInt( cGlobals->params->pDb, key, 1 );
} }
} }
@ -1014,7 +1018,8 @@ rematch_and_save_once( CursesBoardGlobals* bGlobals )
} }
static XP_Bool static XP_Bool
rematch_and_save( CursesBoardGlobals* bGlobals, XP_U32* newGameIDP ) rematch_and_save( CursesBoardGlobals* bGlobals, RematchOrder ro,
XP_U32* newGameIDP )
{ {
LOG_FUNC(); LOG_FUNC();
CommonGlobals* cGlobals = &bGlobals->cGlobals; CommonGlobals* cGlobals = &bGlobals->cGlobals;
@ -1025,7 +1030,7 @@ rematch_and_save( CursesBoardGlobals* bGlobals, XP_U32* newGameIDP )
XP_Bool success = game_makeRematch( &bGlobals->cGlobals.game, NULL_XWE, XP_Bool success = game_makeRematch( &bGlobals->cGlobals.game, NULL_XWE,
bGlobalsNew->cGlobals.util, bGlobalsNew->cGlobals.util,
&cGlobals->cp, &bGlobalsNew->cGlobals.procs, &cGlobals->cp, &bGlobalsNew->cGlobals.procs,
&bGlobalsNew->cGlobals.game, "newName" ); &bGlobalsNew->cGlobals.game, "newName", ro );
if ( success ) { if ( success ) {
if ( !!newGameIDP ) { if ( !!newGameIDP ) {
*newGameIDP = bGlobalsNew->cGlobals.gi->gameID; *newGameIDP = bGlobalsNew->cGlobals.gi->gameID;
@ -1038,13 +1043,13 @@ rematch_and_save( CursesBoardGlobals* bGlobals, XP_U32* newGameIDP )
} }
static void static void
curses_util_notifyGameOver( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_S16 quitter ) curses_util_notifyGameOver( XW_UtilCtxt* uc, XWEnv xwe, XP_S16 quitter )
{ {
LOG_FUNC(); LOG_FUNC();
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals; CommonGlobals* cGlobals = &bGlobals->cGlobals;
LaunchParams* params = cGlobals->params; LaunchParams* params = cGlobals->params;
board_draw( cGlobals->game.board, NULL_XWE ); board_draw( cGlobals->game.board, xwe );
/* game belongs in cGlobals... */ /* game belongs in cGlobals... */
if ( params->printHistory ) { if ( params->printHistory ) {
@ -1057,14 +1062,15 @@ curses_util_notifyGameOver( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_S16 quitte
sleep( params->quitAfter ); sleep( params->quitAfter );
handleQuit( bGlobals->cbState->aGlobals, 0 ); handleQuit( bGlobals->cbState->aGlobals, 0 );
} else if ( params->undoWhenDone ) { } else if ( params->undoWhenDone ) {
server_handleUndo( cGlobals->game.server, NULL_XWE, 0 ); server_handleUndo( cGlobals->game.server, xwe, 0 );
} else if ( !params->skipGameOver && !!bGlobals->boardWin ) { } else if ( !params->skipGameOver && !!bGlobals->boardWin ) {
/* This is modal. Don't show if quitting */ /* This is modal. Don't show if quitting */
cursesShowFinalScores( bGlobals ); cursesShowFinalScores( bGlobals );
} }
if ( params->rematchOnDone ) { if ( params->rematchOnDone ) {
rematch_and_save_once( bGlobals ); XP_ASSERT( !!params->rematchOrder );
rematch_and_save_once( bGlobals, roFromStr(params->rematchOrder) );
} }
} /* curses_util_notifyGameOver */ } /* curses_util_notifyGameOver */

View file

@ -53,7 +53,8 @@ void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ); const XP_U8* buf, XP_U16 len, const CommsAddrRec* from );
void cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel, void cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel,
const CommsAddrRec* destAddr ); const CommsAddrRec* destAddr );
XP_Bool cb_makeRematch( CursesBoardState* cbState, XP_U32 gameID, XP_U32* newGameID ); XP_Bool cb_makeRematch( CursesBoardState* cbState, XP_U32 gameID,
RematchOrder ro, XP_U32* newGameID );
XP_Bool cb_makeMoveIf( CursesBoardState* cbState, XP_U32 gameID ); XP_Bool cb_makeMoveIf( CursesBoardState* cbState, XP_U32 gameID );
const CommonGlobals* cb_getForGameID( CursesBoardState* cbState, XP_U32 gameID ); const CommonGlobals* cb_getForGameID( CursesBoardState* cbState, XP_U32 gameID );

View file

@ -1530,7 +1530,6 @@ makeGameFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
params->localName ); params->localName );
for ( int ii = 0; ii < gi.nPlayers; ++ii ) { for ( int ii = 0; ii < gi.nPlayers; ++ii ) {
gi.players[ii].isLocal = ii == hostPosn; gi.players[ii].isLocal = ii == hostPosn;
gi.players[ii].robotIQ = 1;
} }
tmp = cJSON_GetObjectItem( args, "dict" ); tmp = cJSON_GetObjectItem( args, "dict" );
@ -1601,8 +1600,11 @@ rematchFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
XP_U32 gameID = gidFromObject( args ); XP_U32 gameID = gidFromObject( args );
cJSON* tmp = cJSON_GetObjectItem( args, "rematchOrder" );
RematchOrder ro = roFromStr( tmp->valuestring );
XP_U32 newGameID = 0; XP_U32 newGameID = 0;
if ( cb_makeRematch( aGlobals->cbState, gameID, &newGameID ) ) { if ( cb_makeRematch( aGlobals->cbState, gameID, ro, &newGameID ) ) {
result = newGameID; result = newGameID;
} }
return result; return result;

View file

@ -75,6 +75,26 @@ gtkask_timeout( GtkWidget* parent, const gchar* message,
LOG_RETURNF( "%d", response ); LOG_RETURNF( "%d", response );
return response; return response;
} /* gtkask */ } /* gtkask_timeout */
bool
gtkask_radios( GtkWidget* parent, const gchar *message,
const AskPair* buttxts, int* chosen )
{
gint askResponse = gtkask_timeout( parent, message, GTK_BUTTONS_CANCEL, buttxts, 0 );
bool result = askResponse != GTK_RESPONSE_CANCEL;
if ( result ) {
for ( int ii = 0; ; ++ii ) {
if ( !buttxts[ii].txt ) {
XP_ASSERT(0);
break;
} else if ( askResponse == buttxts[ii].result ) {
*chosen = ii;
break;
}
}
}
return result;
}
#endif #endif

View file

@ -1,6 +1,6 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/* /*
* Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. * Copyright 2000-2023 by Eric House (xwords@eehouse.org). All rights reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -40,5 +40,11 @@ gint gtkask_timeout( GtkWidget* parent, const gchar *message,
GtkButtonsType buttons, const AskPair* buttxts, GtkButtonsType buttons, const AskPair* buttxts,
uint32_t timeoutMS ); uint32_t timeoutMS );
/* Put up buttxts as radio buttons/single choice with and OK button to confirm
and a cancel. That's later; for now just call gtkask() with a ton of
buttons. */
bool gtkask_radios( GtkWidget* parent, const gchar *message,
const AskPair* buttxts, int* chosen );
#endif #endif
#endif /* PLATFORM_GTK */ #endif /* PLATFORM_GTK */

View file

@ -1095,8 +1095,12 @@ makeMenus( GtkGameGlobals* globals )
static void static void
disenable_buttons( GtkGameGlobals* globals ) disenable_buttons( GtkGameGlobals* globals )
{ {
XP_U16 nPending = server_getPendingRegs( globals->cGlobals.game.server ); XWGame* game = &globals->cGlobals.game;
if ( !globals->invite_button && 0 < nPending && !!globals->buttons_hbox ) { XP_U16 nPending = server_getPendingRegs( game->server );
if ( !globals->invite_button
&& 0 < nPending
&& !server_isFromRematch( game->server )
&& !!globals->buttons_hbox ) {
globals->invite_button = globals->invite_button =
addButton( globals->buttons_hbox, "Invite", addButton( globals->buttons_hbox, "Invite",
G_CALLBACK(handle_invite_button), globals ); G_CALLBACK(handle_invite_button), globals );
@ -1584,7 +1588,7 @@ ask_tiles( gpointer data )
} }
return 0; return 0;
} } /* ask_tiles */
static void static void
gtk_util_informNeedPickTiles( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), gtk_util_informNeedPickTiles( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),

View file

@ -1083,7 +1083,7 @@ formatScoreText( PangoLayout* layout, XP_UCHAR* buf, XP_U16 bufLen,
{ {
XP_S16 score = dsi->totalScore; XP_S16 score = dsi->totalScore;
XP_U16 nTilesLeft = dsi->nTilesLeft; XP_U16 nTilesLeft = dsi->nTilesLeft;
XP_Bool isTurn = dsi->isTurn; XP_Bool isTurn = XP_TRUE; // dsi->isTurn;
XP_S16 maxWidth = bounds->width; XP_S16 maxWidth = bounds->width;
XP_UCHAR numBuf[16]; XP_UCHAR numBuf[16];
int width, height; int width, height;

View file

@ -363,6 +363,32 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure )
void void
make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ) make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
{ {
XP_Bool canOffer;
XP_Bool canRematch = server_canRematch( cGlobals->game.server, &canOffer );
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 }
};
gint response;
if ( gtkask_radios( apg->window, "rematch? choose new order",
buttons, &response ) ) {
ro = buttons[response].result;
} else {
goto exit;
}
}
LaunchParams* params = apg->cag.params; LaunchParams* params = apg->cag.params;
GtkGameGlobals* newGlobals = calloc( 1, sizeof(*newGlobals) ); GtkGameGlobals* newGlobals = calloc( 1, sizeof(*newGlobals) );
initBoardGlobalsGtk( newGlobals, params, NULL ); initBoardGlobalsGtk( newGlobals, params, NULL );
@ -373,13 +399,15 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
snprintf( buf, VSIZE(buf), "Game %lX", XP_RANDOM() % 256 ); snprintf( buf, VSIZE(buf), "Game %lX", XP_RANDOM() % 256 );
game_makeRematch( &cGlobals->game, NULL_XWE, util, cp, game_makeRematch( &cGlobals->game, NULL_XWE, util, cp,
&newGlobals->cGlobals.procs, &newGlobals->cGlobals.procs,
&newGlobals->cGlobals.game, buf ); &newGlobals->cGlobals.game, buf, ro );
linuxSaveGame( &newGlobals->cGlobals ); linuxSaveGame( &newGlobals->cGlobals );
sqlite3_int64 rowid = newGlobals->cGlobals.rowid; sqlite3_int64 rowid = newGlobals->cGlobals.rowid;
freeGlobals( newGlobals ); freeGlobals( newGlobals );
open_row( apg, rowid, XP_TRUE ); open_row( apg, rowid, XP_TRUE );
exit:
return;
} /* make_rematch */ } /* make_rematch */
static void static void

View file

@ -409,7 +409,7 @@ makeNewGameDialog( GtkNewGameState* state )
GtkWidget* hbox; GtkWidget* hbox;
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
GtkWidget* roleCombo; GtkWidget* roleCombo;
char* roles[] = { "Standalone", "Host", "Guest" }; char* roles[] = { "Standalone", "Host" };
#endif #endif
dialog = gtk_dialog_new(); dialog = gtk_dialog_new();
@ -641,9 +641,14 @@ gtk_newgame_col_set( void* closure, XP_U16 player, NewGameColumn col,
gchar buf[32]; gchar buf[32];
cp = !!value.ng_cp ? value.ng_cp : ""; cp = !!value.ng_cp ? value.ng_cp : "";
if ( NG_COL_NAME == col && '\0' == cp[0] ) { if ( NG_COL_NAME == col && '\0' == cp[0] ) {
LaunchParams* params = state->globals->cGlobals.params;
if ( !!params->localName ) {
cp = params->localName;
} else {
sprintf( buf, "Linuser %d", 1 + player ); sprintf( buf, "Linuser %d", 1 + player );
cp = buf; cp = buf;
} }
}
gtk_entry_set_text( GTK_ENTRY(widget), cp ); gtk_entry_set_text( GTK_ENTRY(widget), cp );
break; break;
} }

View file

@ -2533,6 +2533,32 @@ makeSelfAddress( CommsAddrRec* selfAddr, const LaunchParams* params )
} }
} }
RematchOrder
roFromStr(const char* rematchOrder )
{
RematchOrder result;
struct {
char* str;
RematchOrder ro;
} vals [] = {
{ "same", RO_SAME },
{ "low_score_first", RO_LOW_SCORE_FIRST },
{ "high_score_first", RO_HIGH_SCORE_FIRST },
{ "juggle", RO_JUGGLE },
#ifdef XWFEATURE_RO_BYNAME
{ "by_name", RO_BY_NAME },
#endif
};
for ( int ii = 0; ii < VSIZE(vals); ++ii ) {
if ( 0 == strcmp( rematchOrder, vals[ii].str ) ) {
result = vals[ii].ro;
break;
}
}
XP_LOGFF( "(%s) => %d", rematchOrder, result );
return result;
}
static void static void
writeStatus( const char* statusSocket, const char* dbName ) writeStatus( const char* statusSocket, const char* dbName )
{ {

View file

@ -117,6 +117,8 @@ void tryConnectToServer( CommonGlobals* cGlobals );
void ensureLocalPlayerNames( LaunchParams* params, CurGameInfo* gi ); void ensureLocalPlayerNames( LaunchParams* params, CurGameInfo* gi );
void cancelTimers( CommonGlobals* cGlobals ); void cancelTimers( CommonGlobals* cGlobals );
RematchOrder roFromStr(const char* rematchOrder );
/* void initParams( LaunchParams* params ); */ /* void initParams( LaunchParams* params ); */
/* void freeParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/* /*
* Copyright 2001 - 2020 by Eric House (xwords@eehouse.org). All rights * Copyright 2001 - 2023 by Eric House (xwords@eehouse.org). All rights
* reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -157,6 +157,8 @@ typedef struct _LaunchParams {
const XP_UCHAR* iterTestPatStr; const XP_UCHAR* iterTestPatStr;
#endif #endif
const char* rematchOrder;
struct { struct {
void (*quit)(void* params); void (*quit)(void* params);
} cmdProcs; } cmdProcs;

View file

@ -4,6 +4,8 @@ import argparse, datetime, json, os, random, shutil, signal, \
socket, struct, subprocess, sys, threading, time socket, struct, subprocess, sys, threading, time
g_NAMES = ['Brynn', 'Ariela', 'Kati', 'Eric'] g_NAMES = ['Brynn', 'Ariela', 'Kati', 'Eric']
# These must correspond to what the linux app is looking for in roFromStr()
g_ROS = ['same', 'low_score_first', 'high_score_first', 'juggle', 'by_name',]
gDone = False gDone = False
gGamesMade = 0 gGamesMade = 0
g_LOGFILE = None g_LOGFILE = None
@ -292,7 +294,9 @@ class Device():
# way. But how I figure out the other players differs. # way. But how I figure out the other players differs.
def rematch(self, game): def rematch(self, game):
gid = game.gid gid = game.gid
newGid = self._sendWaitReply('rematch', gid=gid).get('newGid') rematchOrder = self.figureRematchOrder()
newGid = self._sendWaitReply('rematch', gid=gid, rematchOrder=rematchOrder) \
.get('newGid')
if newGid: if newGid:
guests = Device.playersIn(gid) guests = Device.playersIn(gid)
guests.remove(self.host) guests.remove(self.host)
@ -326,6 +330,11 @@ class Device():
self.guestGames.append(GuestGameInfo(self, gid, rematchLevel)) self.guestGames.append(GuestGameInfo(self, gid, rematchLevel))
self.launchIfNot() self.launchIfNot()
def figureRematchOrder(self):
ro = self.args.REMATCH_ORDER
if not ro: ro = random.choice(g_ROS)
return ro
# Return true only if all games I host are finished on all games. # Return true only if all games I host are finished on all games.
# But: what about games I don't host? For now, let's make it all # But: what about games I don't host? For now, let's make it all
# games! # games!
@ -671,6 +680,8 @@ def mkParser():
parser.add_argument('--rematch-level', dest = 'REMATCH_LEVEL', type = int, default = 0, parser.add_argument('--rematch-level', dest = 'REMATCH_LEVEL', type = int, default = 0,
help = 'rematch games down to this ancestry/depth') help = 'rematch games down to this ancestry/depth')
parser.add_argument('--rematch-order', dest = 'REMATCH_ORDER', type = str, default = None,
help = 'order rematched games one of these ways: {}'.format(g_ROS))
# envpat = 'DISCON_COREPAT' # envpat = 'DISCON_COREPAT'
# parser.add_argument('--core-pat', dest = 'CORE_PAT', default = os.environ.get(envpat), # parser.add_argument('--core-pat', dest = 'CORE_PAT', default = os.environ.get(envpat),

View file

@ -1,15 +0,0 @@
#!/bin/bash
# I use this thing this way: playnum.sh 10 2>&1 | ./wordlens.pl
NUM=$1
echo "NUM=$NUM"
while :; do
../linux/xwords -u -s -r Eric -d ../linux/dicts/OSPD2to15.xwd -q -i
NUM=$(( NUM - 1 ));
if (( $NUM <= 0 )); then exit 0; fi
done