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,
CHANGE_CONN,
GAMES_LIST_NAME_REMATCH,
GAMES_LIST_GET_RO,
ASK_DUP_PAUSE,
CHOOSE_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.XwJNI;
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.Utils.ISOCode;
@ -72,6 +73,14 @@ public class GameUtils {
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;
// 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
@ -574,7 +583,8 @@ public class GameUtils {
}
public static long makeRematch( Context context, long srcRowid,
long groupID, String gameName )
long groupID, String gameName,
NeedRematchOrder nro )
{
long rowid = DBUtils.ROWID_NOTFOUND;
try ( GameLock lock = GameLock.tryLockRO( srcRowid ) ) {
@ -582,13 +592,22 @@ public class GameUtils {
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 ) ) {
if ( null != gamePtrNew ) {
rowid = saveNewGame1( context, gamePtrNew,
groupID, gameName );
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 );
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 );
}
}
}
}

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.LastMoveInfo;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
import org.eehouse.android.xw4.loc.LocUtils;
import static org.eehouse.android.xw4.DBUtils.ROWID_NOTFOUND;
@ -874,6 +875,12 @@ public class GamesListDelegate extends ListDelegateBase
}
break;
case GAMES_LIST_GET_RO: {
NRO nro = (NRO)params[0];
dialog = mkRematchConfigDlg( nro );
}
break;
case GAMES_LIST_NAME_REMATCH: {
final LinearLayout view = (LinearLayout)
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 );
}
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 )
{
final BackupConfigView view = (BackupConfigView)
@ -2358,6 +2389,7 @@ public class GamesListDelegate extends ListDelegateBase
button.setVisibility( View.VISIBLE );
final boolean solo = isSolos[ii];
button.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View view ) {
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 )
{
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() ) {
Bundle extras = m_rematchExtras;
@ -2720,17 +2797,19 @@ public class GamesListDelegate extends ListDelegateBase
DBUtils.GROUPID_UNSPEC );
long newid = GameUtils.makeRematch( m_activity, srcRowID,
groupID, gameName );
groupID, gameName, nro );
if ( extras.getBoolean( REMATCH_DELAFTER_EXTRA, false ) ) {
String name = DBUtils.getName( m_activity, srcRowID );
makeConfirmThenBuilder( Action.LAUNCH_AFTER_DEL,
R.string.confirm_del_after_rematch_fmt,
name )
.setParams( newid, srcRowID )
.show();
} else {
launchGame( newid );
if ( DBUtils.ROWID_NOTFOUND != newid ) {
if ( extras.getBoolean( REMATCH_DELAFTER_EXTRA, false ) ) {
String name = DBUtils.getName( m_activity, srcRowID );
makeConfirmThenBuilder( Action.LAUNCH_AFTER_DEL,
R.string.confirm_del_after_rematch_fmt,
name )
.setParams( newid, srcRowID )
.show();
} else {
launchGame( newid );
}
}
}
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.CommsConnTypeSet;
import org.eehouse.android.xw4.jni.XwJNI.RematchOrder;
public class XWPrefs {
private static final String TAG = XWPrefs.class.getSimpleName();
@ -328,6 +330,30 @@ public class XWPrefs {
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 )
{
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.NetLaunchInfo;
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;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -336,11 +337,24 @@ public class XwJNI {
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,
CommonPrefs cp, String gameName )
CommonPrefs cp, String gameName,
RematchOrder ro )
{
GamePtr gamePtrNew = initGameJNI( 0 );
if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName ) ) {
if ( !game_makeRematch( gamePtr, gamePtrNew, util, cp, gameName, ro ) ) {
gamePtrNew.release();
gamePtrNew = null;
}
@ -384,7 +398,7 @@ public class XwJNI {
private static native boolean game_makeRematch( GamePtr gamePtr,
GamePtr gamePtrNew,
UtilCtxt util, CommonPrefs cp,
String gameName );
String gameName, RematchOrder ro );
private static native boolean game_makeFromInvite( GamePtr gamePtr, NetLaunchInfo nli,
UtilCtxt util,
@ -536,6 +550,7 @@ 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 native void server_endGame( GamePtr gamePtr );
// 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_default_robodict">key_default_robodict</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_notify_sound">key_notify_sound</string>
<string name="key_disable_mqtt">key_disable_mqtt</string>
@ -222,6 +223,14 @@
<item>@string/phonies_block</item>
</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">
<item>@string/robot_smartest</item>
<item>@string/robot_smarter</item>

View file

@ -5,4 +5,12 @@
<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>

View file

@ -16,6 +16,13 @@
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"
app:summary="@string/prefs_dicts_summary"
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
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch
( 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;
XWJNI_START_GLOBALS(gamePtr);
@ -1437,8 +1437,10 @@ 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 );
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 );
if ( success ) {
@ -2200,6 +2202,19 @@ Java_org_eehouse_android_xw4_jni_XwJNI_server_1initClientConnection
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
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1start
( JNIEnv* env, jclass C, GamePtrType gamePtr )

View file

@ -239,6 +239,7 @@ struct CommsCtxt {
#define FLAG_HARVEST_DONE 1
#define FLAG_QUASHED 2
#define QUASHED(COMMS) (0 != ((COMMS)->flags & FLAG_QUASHED))
#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
|| streamVersion == comms->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 );
THREAD_CHECK_END();
} /* comms_setConnID */
@ -724,6 +725,7 @@ addrFromStreamOne( CommsAddrRec* addrP, XWStreamCtxt* stream, CommsConnType typ
void
addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream )
{
XP_MEMSET( addrP, 0, sizeof(*addrP) );
XP_U8 tmp = stream_getU8( stream );
XP_U16 version = stream_getVersion( stream );
XP_ASSERT( 0 < version );
@ -1381,6 +1383,41 @@ comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
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 {
int count;
} NonAcks;
@ -3604,16 +3641,16 @@ static void
logAddrComms( const CommsCtxt* comms, const CommsAddrRec* addr,
const char* caller )
{
logAddr( MPPARM(comms->mpool) comms->dutil, addr, caller );
logAddr( comms->dutil, addr, caller );
}
void
logAddr( MPFORMAL XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
logAddr( XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
const char* caller )
{
if ( !!addr ) {
char buf[128];
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(mpool)
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(dutil->mpool)
dutil_getVTManager(dutil));
if ( !!caller ) {
snprintf( buf, sizeof(buf), "called on %p from %s:\n",
@ -3872,6 +3909,14 @@ types_hasType( XP_U16 conTypes, CommsConnType typ )
return hasType;
}
XP_Bool
addr_isEmpty( const CommsAddrRec* addr )
{
CommsConnType typ;
XP_U32 st = 0;
return !addr_iter( addr, &typ, &st );
}
CommsConnType
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 );
void comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
CommsAddrRec* addr );
XP_Bool comms_addrsAreSame( const CommsCtxt* comms, const CommsAddrRec* addr1,
const CommsAddrRec* addr2 );
#ifdef XWFEATURE_COMMSACK
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 isNewer );
XP_Bool addr_isEmpty( const CommsAddrRec* addr );
CommsConnType addr_getType( const CommsAddrRec* addr );
void addr_setType( 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 comms_getAddrDisabled( const CommsCtxt* comms, CommsConnType typ,
XP_Bool send );
void logAddr( MPFORMAL XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
void logAddr( XW_DUtilCtxt* dutil, const CommsAddrRec* addr,
const char* caller );
# else

View file

@ -48,6 +48,7 @@
#define MAX_COLS MAX_ROWS
#define MIN_COLS 11
#define STREAM_VERS_REMATCHORDER 0x25
#define STREAM_VERS_REMATCHADDRS 0x24
#define STREAM_VERS_MSGSTREAMVERS 0x23
#define STREAM_VERS_NORELAY 0x22
@ -99,7 +100,7 @@
#define STREAM_VERS_405 0x01
/* 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 {
XP_S16 left;

View file

@ -237,14 +237,14 @@ 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 )
XWGame* newGame, const XP_UCHAR* newName, RematchOrder ro )
{
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,
makeGameID( newUtil ), &ra ) ) {
makeGameID( newUtil ), ro, &rip ) ) {
CommsAddrRec* selfAddrP = NULL;
CommsAddrRec selfAddr;
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,
newUtil->gameInfo, selfAddrP, (CommsAddrRec*)NULL,
newUtil, (DrawCtx*)NULL, newCp, procs ) ) {
if ( !!newGame->comms ) {
server_setRematchOrder( newGame->server, rip );
const CurGameInfo* newGI = newUtil->gameInfo;
for ( int ii = 0; ii < ra.nAddrs; ++ii ) {
NetLaunchInfo nli;
/* hard-code one player per device -- for now */
nli_init( &nli, newGI, selfAddrP, 1, ii + 1 );
if ( !!newName ) {
nli_setGameName( &nli, newName );
const CurGameInfo* newGI = newUtil->gameInfo;
for ( int ii = 0; ; ++ii ) {
CommsAddrRec guestAddr;
XP_U16 nPlayersH;
if ( !server_ri_getAddr( rip, ii, &guestAddr, &nPlayersH ) ) {
break;
}
XP_ASSERT( !comms_addrsAreSame( newGame->comms, &guestAddr,
&selfAddr ) );
NetLaunchInfo nli;
nli_init( &nli, newGI, selfAddrP, nPlayersH, ii + 1 );
if ( !!newName ) {
nli_setGameName( &nli, newName );
}
LOGNLI( &nli );
comms_invite( newGame->comms, xwe, &nli, &guestAddr, XP_TRUE );
}
LOGNLI( &nli );
comms_invite( newGame->comms, xwe, &nli, &ra.addrs[ii], XP_TRUE );
}
success = XP_TRUE;
}
server_disposeRematchInfo( oldGame->server, &rip );
}
LOG_RETURNF( "%s", boolToStr(success) );
return success;
@ -518,8 +529,7 @@ game_getState( const XWGame* game, XWEnv xwe, GameStateInfo* gsi )
gsi->canTrade = board_canTrade( board, xwe );
gsi->nPendingMessages = !!game->comms ?
comms_countPendingPackets(game->comms, NULL) : 0;
gsi->canRematch = server_canRematch( server );
gsi->canRematch = server_canRematch( server, NULL );
gsi->canPause = server_canPause( 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
#endif
);
XP_Bool game_makeRematch( const XWGame* game, XWEnv xwe, XW_UtilCtxt* util,
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,
DictionaryCtxt* dict );

View file

@ -275,7 +275,7 @@ typedef struct WordNotifierInfo {
XP_Bool getCurrentMoveScoreIfLegal( ModelCtxt* model, XWEnv xwe,
XP_S16 turn, XWStreamCtxt* stream,
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,
LastMoveInfo* info );
@ -299,9 +299,12 @@ XP_Bool model_checkMoveLegal( ModelCtxt* model, XWEnv xwe, XP_S16 player,
WordNotifierInfo* notifyInfo );
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 );
void model_getCurScores( const ModelCtxt* model, ScoresArray* scores,
XP_Bool gameOver );
/* figureMoveScore is meant only for the engine's use */
XP_U16 figureMoveScore( const ModelCtxt* model, XWEnv xwe, XP_U16 turn,
const MoveInfo* mvInfo, EngineCtxt* engine,

View file

@ -145,7 +145,7 @@ getCurrentMoveScoreIfLegal( ModelCtxt* model, XWEnv xwe, XP_S16 turn,
} /* getCurrentMoveScoreIfLegal */
XP_S16
model_getPlayerScore( ModelCtxt* model, XP_S16 player )
model_getPlayerScore( const ModelCtxt* model, XP_S16 player )
{
return model->players[player].score;
} /* model_getPlayerScore */
@ -155,7 +155,7 @@ model_getPlayerScore( ModelCtxt* model, XP_S16 player )
* player.
*/
void
model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
model_figureFinalScores( const ModelCtxt* model, ScoresArray* finalScoresP,
ScoresArray* tilePenaltiesP )
{
XP_S16 ii, jj;
@ -164,7 +164,7 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
XP_U16 nPlayers = model->nPlayers;
XP_S16 firstDoneIndex = -1; /* not set unless FIRST_DONE_BONUS is set */
const TrayTileSet* tray;
PlayerCtxt* player;
const PlayerCtxt* player;
const DictionaryCtxt* dict = model_getDictionary( model );
CurGameInfo* gi = model->vol.gi;
@ -217,6 +217,20 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
}
} /* 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 {
ModelCtxt* model;
XWStreamCtxt* stream;

View file

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

View file

@ -200,16 +200,10 @@ drawScoreBoard( BoardCtxt* board, XWEnv xwe )
#endif
/* Get the scores from the model or by calculating them based on
the end-of-game state. */
if ( board->gameOver ) {
model_figureFinalScores( model, &scores, NULL );
} else {
for ( ii = 0; ii < nPlayers; ++ii ) {
scores.arr[ii] = model_getPlayerScore( model, ii );
}
}
model_getCurScores( model, &scores, board->gameOver );
if ( draw_scoreBegin( board->draw, xwe, &board->scoreBdBounds, nPlayers,
scores.arr, nTilesInPool,
if ( draw_scoreBegin( board->draw, xwe, &board->scoreBdBounds,
nPlayers, scores.arr, nTilesInPool,
dfsFor( board, OBJ_SCORE ) ) ) {
XP_U16 totalDim = 0; /* not counting rem */
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_canPause( 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_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 );
#endif
typedef struct _RematchAddrs {
CommsAddrRec addrs[MAX_NUM_PLAYERS];
XP_U16 nAddrs;
} RematchAddrs;
typedef enum {
RO_SAME, /* preserve the parent game's order */
RO_LOW_SCORE_FIRST, /* lowest scorer in parent goes first, etc */
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
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, 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
}

View file

@ -518,6 +518,17 @@ randIntArray( XP_U16* rnums, XP_U16 count )
return changed;
} /* randIntArray */
XP_U16
countBits( XP_U32 mask )
{
XP_U16 result = 0;
while ( 0 != mask ) {
++result;
mask &= mask - 1;
}
return result;
}
#ifdef XWFEATURE_BASE64
/* 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

View file

@ -122,6 +122,8 @@ XP_UCHAR* emptyStringIfNull( XP_UCHAR* str );
/* Produce an array of ints 0..count-1, juggled */
XP_Bool randIntArray( XP_U16* rnums, XP_U16 count );
XP_U16 countBits( XP_U32 mask );
#ifdef XWFEATURE_BASE64
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 );

View file

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

View file

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

View file

@ -75,6 +75,26 @@ gtkask_timeout( GtkWidget* parent, const gchar* message,
LOG_RETURNF( "%d", 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

View file

@ -1,6 +1,6 @@
/* -*-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
* 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,
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 /* PLATFORM_GTK */

View file

@ -1095,8 +1095,12 @@ makeMenus( GtkGameGlobals* globals )
static void
disenable_buttons( GtkGameGlobals* globals )
{
XP_U16 nPending = server_getPendingRegs( globals->cGlobals.game.server );
if ( !globals->invite_button && 0 < nPending && !!globals->buttons_hbox ) {
XWGame* game = &globals->cGlobals.game;
XP_U16 nPending = server_getPendingRegs( game->server );
if ( !globals->invite_button
&& 0 < nPending
&& !server_isFromRematch( game->server )
&& !!globals->buttons_hbox ) {
globals->invite_button =
addButton( globals->buttons_hbox, "Invite",
G_CALLBACK(handle_invite_button), globals );
@ -1584,7 +1588,7 @@ ask_tiles( gpointer data )
}
return 0;
}
} /* ask_tiles */
static void
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_U16 nTilesLeft = dsi->nTilesLeft;
XP_Bool isTurn = dsi->isTurn;
XP_Bool isTurn = XP_TRUE; // dsi->isTurn;
XP_S16 maxWidth = bounds->width;
XP_UCHAR numBuf[16];
int width, height;

View file

@ -363,6 +363,32 @@ 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_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;
GtkGameGlobals* newGlobals = calloc( 1, sizeof(*newGlobals) );
initBoardGlobalsGtk( newGlobals, params, NULL );
@ -373,13 +399,15 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
snprintf( buf, VSIZE(buf), "Game %lX", XP_RANDOM() % 256 );
game_makeRematch( &cGlobals->game, NULL_XWE, util, cp,
&newGlobals->cGlobals.procs,
&newGlobals->cGlobals.game, buf );
&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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/* -*- 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.
*
* This program is free software; you can redistribute it and/or
@ -157,6 +157,8 @@ typedef struct _LaunchParams {
const XP_UCHAR* iterTestPatStr;
#endif
const char* rematchOrder;
struct {
void (*quit)(void* params);
} cmdProcs;

View file

@ -4,6 +4,8 @@ import argparse, datetime, json, os, random, shutil, signal, \
socket, struct, subprocess, sys, threading, time
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
gGamesMade = 0
g_LOGFILE = None
@ -292,7 +294,9 @@ class Device():
# way. But how I figure out the other players differs.
def rematch(self, game):
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:
guests = Device.playersIn(gid)
guests.remove(self.host)
@ -326,6 +330,11 @@ class Device():
self.guestGames.append(GuestGameInfo(self, gid, rematchLevel))
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.
# But: what about games I don't host? For now, let's make it all
# games!
@ -671,6 +680,8 @@ def mkParser():
parser.add_argument('--rematch-level', dest = 'REMATCH_LEVEL', type = int, default = 0,
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'
# 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