diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml
index 82f66b86d..8173b8ba9 100644
--- a/xwords4/android/XWords4/res/values/strings.xml
+++ b/xwords4/android/XWords4/res/values/strings.xml
@@ -50,6 +50,8 @@
Cancel
Yes
No
+ Save
+ Discard
Name:
Open
@@ -161,7 +163,13 @@
Download more...
http://eehouse.org/and_dicts
+
+ Confirm save
+ This game is in play. If you save
+ these changes it must be restarted. Do you want to save or
+ discard these changes?
+
Allow hints
Enable timer
Color tiles
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
index f119d09c4..bc465a2eb 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java
@@ -41,6 +41,7 @@ import android.widget.CompoundButton;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuInflater;
+import android.view.KeyEvent;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
@@ -59,11 +60,13 @@ public class GameConfig extends Activity implements View.OnClickListener {
private static final int ROLE_EDIT_SMS = 3;
private static final int ROLE_EDIT_BT = 4;
private static final int FORCE_REMOTE = 5;
+ private static final int CONFIRM_CHANGE = 6;
private Button m_addPlayerButton;
private Button m_configureButton;
private String m_path;
private CurGameInfo m_gi;
+ private CurGameInfo m_giOrig;
private int m_whichPlayer;
private Dialog m_curDialog;
private Spinner m_roleSpinner;
@@ -73,10 +76,12 @@ public class GameConfig extends Activity implements View.OnClickListener {
private String[] m_dicts;
private int m_browsePosition;
private LinearLayout m_playerLayout;
+ private CommsAddrRec m_carOrig;
private CommsAddrRec m_car;
private CommonPrefs m_cp;
private boolean m_canDoSMS = false;
private boolean m_canDoBT = false;
+ private int m_nMoves = 0;
private CommsAddrRec.CommsConnType[] m_types;
class RemoteChoices implements ListAdapter {
@@ -174,6 +179,27 @@ public class GameConfig extends Activity implements View.OnClickListener {
}
});
break;
+ case CONFIRM_CHANGE:
+ dialog = new AlertDialog.Builder( this )
+ .setTitle( R.string.confirm_save_title )
+ .setMessage( R.string.confirm_save )
+ .setPositiveButton( R.string.button_save,
+ new DialogInterface.OnClickListener() {
+ public void onClick( DialogInterface dlg,
+ int whichButton ) {
+ applyChanges( true );
+ finish();
+ }
+ })
+ .setNegativeButton( R.string.button_discard,
+ new DialogInterface.OnClickListener() {
+ public void onClick( DialogInterface dlg,
+ int whichButton ) {
+ finish();
+ }
+ })
+ .create();
+ break;
}
return dialog;
}
@@ -312,28 +338,25 @@ public class GameConfig extends Activity implements View.OnClickListener {
m_path = m_path.substring( 1 );
}
- byte[] stream = Utils.savedGame( this, m_path );
- m_gi = new CurGameInfo( this );
- XwJNI.gi_from_stream( m_gi, stream );
- byte[] dictBytes = Utils.openDict( this, m_gi.dictName );
-
int gamePtr = XwJNI.initJNI();
- if ( !XwJNI.game_makeFromStream( gamePtr, stream, JNIUtilsImpl.get(),
- m_gi, dictBytes, m_cp ) ) {
- XwJNI.game_makeNewGame( gamePtr, m_gi, JNIUtilsImpl.get(),
- m_cp, dictBytes );
- }
+ m_giOrig = new CurGameInfo( this );
+ Utils.loadMakeGame( this, gamePtr, m_giOrig, m_path );
+ m_nMoves = XwJNI.model_getNMoves( gamePtr );
+ m_giOrig.setInProgress( 0 < m_nMoves );
+ m_gi = new CurGameInfo( m_giOrig );
int curSel = listAvailableDicts( m_gi.dictName );
- m_car = new CommsAddrRec();
+ m_carOrig = new CommsAddrRec();
if ( XwJNI.game_hasComms( gamePtr ) ) {
- XwJNI.comms_getAddr( gamePtr, m_car );
+ XwJNI.comms_getAddr( gamePtr, m_carOrig );
} else {
- XwJNI.comms_getInitialAddr( m_car );
+ XwJNI.comms_getInitialAddr( m_carOrig );
}
XwJNI.game_dispose( gamePtr );
+ m_car = new CommsAddrRec( m_carOrig );
+
setContentView(R.layout.game_config);
m_addPlayerButton = (Button)findViewById(R.id.add_player);
@@ -439,13 +462,6 @@ public class GameConfig extends Activity implements View.OnClickListener {
adjustVisibility();
} // onCreate
- @Override
- protected void onPause()
- {
- saveChanges();
- super.onPause(); // skip this and get a crash :-)
- }
-
@Override
public void onCreateContextMenu( ContextMenu menu, View view,
ContextMenuInfo menuInfo ) {
@@ -537,6 +553,27 @@ public class GameConfig extends Activity implements View.OnClickListener {
}
} // onClick
+ @Override
+ public boolean onKeyDown( int keyCode, KeyEvent event )
+ {
+ boolean consumed = false;
+ if ( keyCode == KeyEvent.KEYCODE_BACK ) {
+ saveChanges();
+ if ( 0 < m_nMoves && (m_giOrig.changesMatter(m_gi)
+ || m_carOrig.changesMatter(m_car) ) ) {
+ showDialog( CONFIRM_CHANGE );
+ consumed = true;
+ } else {
+ applyChanges( false );
+ }
+ }
+
+ if ( !consumed ) {
+ consumed = super.onKeyDown( keyCode, event );
+ }
+ return consumed;
+ }
+
private void loadPlayers()
{
m_playerLayout.removeAllViews();
@@ -715,17 +752,39 @@ public class GameConfig extends Activity implements View.OnClickListener {
position = m_connectSpinner.getSelectedItemPosition();
m_car.conType = m_types[ position ];
+ }
+ private void applyChanges( boolean forceNew )
+ {
+ // This should be a separate function, commitChanges() or
+ // somesuch. But: do we have a way to save changes to a gi
+ // that don't reset the game, e.g. player name for standalone
+ // games?
byte[] dictBytes = Utils.openDict( this, m_gi.dictName );
int gamePtr = XwJNI.initJNI();
- XwJNI.game_makeNewGame( gamePtr, m_gi, JNIUtilsImpl.get(),
- m_cp, dictBytes );
+ boolean madeGame = false;
+
+ if ( !forceNew ) {
+ byte[] stream = Utils.savedGame( this, m_path );
+ // Will fail if there's nothing in the stream but a gi.
+ madeGame = XwJNI.game_makeFromStream( gamePtr, stream,
+ JNIUtilsImpl.get(),
+ new CurGameInfo(this),
+ dictBytes, m_cp );
+ }
+
+ if ( forceNew || !madeGame ) {
+ m_gi.setInProgress( false );
+ m_gi.fixup();
+ XwJNI.game_makeNewGame( gamePtr, m_gi, JNIUtilsImpl.get(),
+ m_cp, dictBytes );
+ }
if ( null != m_car ) {
XwJNI.comms_setAddr( gamePtr, m_car );
}
- byte[] stream = XwJNI.game_saveToStream( gamePtr, m_gi );
- Utils.saveGame( this, stream, m_path );
+
+ Utils.saveGame( this, gamePtr, m_gi, m_path );
XwJNI.game_dispose( gamePtr );
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
index 638115976..392143ed2 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java
@@ -124,18 +124,12 @@ public class GamesList extends ListActivity implements View.OnClickListener {
break;
case R.id.list_item_reset:
- case R.id.list_item_new_from:
- stream = Utils.savedGame( this, path );
- CurGameInfo gi = new CurGameInfo( this );
- XwJNI.gi_from_stream( gi, stream );
- stream = XwJNI.gi_to_stream( gi );
- if ( R.id.list_item_reset == id ) {
- Utils.saveGame( this, stream, path );
- } else {
- Utils.saveGame( this, stream );
- }
+ Utils.resetGame( this, path, path );
break;
-
+ case R.id.list_item_new_from:
+ Utils.resetGame( this, path );
+ break;
+
case R.id.list_item_copy:
stream = Utils.savedGame( this, path );
Utils.saveGame( this, stream );
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
index 79d443725..ada526d10 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java
@@ -25,6 +25,8 @@ import java.util.Formatter;
import android.view.LayoutInflater;
import junit.framework.Assert;
+import org.eehouse.android.xw4.jni.*;
+
public class Utils {
static final String TAG = "EJAVA";
@@ -97,6 +99,73 @@ public class Utils {
return stream;
} // savedGame
+ /**
+ * Open an existing game, and use its gi and comms addr as the
+ * basis for a new one.
+ */
+ public static void resetGame( Context context, String pathIn,
+ String pathOut )
+ {
+ int gamePtr = XwJNI.initJNI();
+ CurGameInfo gi = new CurGameInfo( context );
+ CommsAddrRec addr = null;
+
+ loadMakeGame( context, gamePtr, gi, pathIn );
+ byte[] dictBytes = Utils.openDict( context, gi.dictName );
+
+ if ( XwJNI.game_hasComms( gamePtr ) ) {
+ addr = new CommsAddrRec();
+ XwJNI.comms_getAddr( gamePtr, addr );
+ }
+ XwJNI.game_dispose( gamePtr );
+
+ gi.setInProgress( false );
+ gi.fixup();
+
+ gamePtr = XwJNI.initJNI();
+ XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(),
+ CommonPrefs.get(), dictBytes );
+ if ( null != addr ) {
+ XwJNI.comms_setAddr( gamePtr, addr );
+ }
+ saveGame( context, gamePtr, gi, pathOut );
+ XwJNI.game_dispose( gamePtr );
+ }
+
+ public static void resetGame( Context context, String pathIn )
+ {
+ resetGame( context, pathIn, newName( context ) );
+ }
+
+ public static void loadMakeGame( Context context, int gamePtr,
+ CurGameInfo gi, String path )
+ {
+ byte[] stream = savedGame( context, path );
+ XwJNI.gi_from_stream( gi, stream );
+ byte[] dictBytes = Utils.openDict( context, gi.dictName );
+
+ boolean madeGame = XwJNI.game_makeFromStream( gamePtr, stream,
+ JNIUtilsImpl.get(),
+ gi, dictBytes,
+ CommonPrefs.get() );
+ if ( !madeGame ) {
+ XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(),
+ CommonPrefs.get(), dictBytes );
+ }
+ }
+
+ public static void saveGame( Context context, int gamePtr,
+ CurGameInfo gi, String path )
+ {
+ byte[] stream = XwJNI.game_saveToStream( gamePtr, gi );
+ saveGame( context, stream, path );
+ }
+
+ public static void saveGame( Context context, int gamePtr,
+ CurGameInfo gi )
+ {
+ saveGame( context, gamePtr, gi, newName( context ) );
+ }
public static void saveGame( Context context, byte[] bytes, String path )
{
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommsAddrRec.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommsAddrRec.java
index e7ccff2c7..31943c047 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommsAddrRec.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommsAddrRec.java
@@ -38,8 +38,20 @@ public class CommsAddrRec {
ip_relay_port = 10999;
}
- public CommsAddrRec( CommsAddrRec src ) {
- this.copyFrom(src );
+ public CommsAddrRec( final CommsAddrRec src )
+ {
+ this.copyFrom( src );
+ }
+
+ public boolean changesMatter( final CommsAddrRec other )
+ {
+ boolean matter = conType != other.conType;
+ if ( !matter ) {
+ matter = ! ip_relay_invite.equals( other.ip_relay_invite )
+ || ! ip_relay_hostName.equals( other.ip_relay_hostName )
+ || ip_relay_port != other.ip_relay_port;
+ }
+ return matter;
}
private void copyFrom( CommsAddrRec src )
@@ -49,13 +61,4 @@ public class CommsAddrRec {
ip_relay_hostName = src.ip_relay_hostName;
ip_relay_port = src.ip_relay_port;
}
-
- private static CommsAddrRec s_car;
- public static final CommsAddrRec get()
- {
- if ( null == s_car ) {
- s_car = new CommsAddrRec();
- }
- return s_car;
- }
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java
index 8d1538556..bf32db209 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CurGameInfo.java
@@ -35,8 +35,10 @@ public class CurGameInfo {
private int[] m_visiblePlayers;
private int m_nVisiblePlayers;
+ private boolean m_inProgress;
public CurGameInfo( Context context ) {
+ m_inProgress = false;
nPlayers = 2;
boardSize = 15;
players = new LocalPlayer[MAX_NUM_PLAYERS];
@@ -57,11 +59,13 @@ public class CurGameInfo {
players[ii] = new LocalPlayer(ii);
}
- m_visiblePlayers = new int[MAX_NUM_PLAYERS];
figureVisible();
}
- public CurGameInfo( CurGameInfo src ) {
+ public CurGameInfo( CurGameInfo src )
+ {
+ m_inProgress = src.m_inProgress;
+ gameID = src.gameID;
nPlayers = src.nPlayers;
boardSize = src.boardSize;
players = new LocalPlayer[MAX_NUM_PLAYERS];
@@ -94,6 +98,43 @@ public class CurGameInfo {
}
}
+ public void setInProgress( boolean inProgress )
+ {
+ m_inProgress = inProgress;
+ figureVisible();
+ }
+
+ /** return true if any of the changes made would invalide a game
+ * in progress, i.e. require that it be restarted with the new
+ * params. E.g. changing a player to a robot is harmless for a
+ * local-only game but illegal for a connected one.
+ */
+ public boolean changesMatter( final CurGameInfo other )
+ {
+ boolean matter = nPlayers != other.nPlayers
+ || serverRole != other.serverRole
+ || !dictName.equals( other.dictName )
+ || boardSize != other.boardSize
+ || hintsNotAllowed != other.hintsNotAllowed
+ || allowPickTiles != other.allowPickTiles
+ || phoniesAction != other.phoniesAction;
+
+ if ( !matter && DeviceRole.SERVER_STANDALONE != serverRole ) {
+ for ( int ii = 0; ii < nPlayers; ++ii ) {
+ LocalPlayer me = players[ii];
+ LocalPlayer him = other.players[ii];
+ matter = me.isRobot != him.isRobot
+ || me.isLocal != him.isLocal
+ || !me.name.equals( him.name );
+ if ( matter ) {
+ break;
+ }
+ }
+ }
+
+ return matter;
+ }
+
public int remoteCount()
{
figureVisible();
@@ -129,7 +170,7 @@ public class CurGameInfo {
nPlayers = m_nVisiblePlayers;
}
- if ( serverRole != DeviceRole.SERVER_ISSERVER ) {
+ if ( !m_inProgress && serverRole != DeviceRole.SERVER_ISSERVER ) {
for ( int ii = 0; ii < nPlayers; ++ii ) {
players[ii].isLocal = true;
}
@@ -263,9 +304,14 @@ public class CurGameInfo {
private void figureVisible()
{
+ if ( null == m_visiblePlayers ) {
+ m_visiblePlayers = new int[MAX_NUM_PLAYERS];
+ }
+
m_nVisiblePlayers = 0;
for ( int ii = 0; ii < nPlayers; ++ii ) {
- if ( serverRole != DeviceRole.SERVER_ISCLIENT
+ if ( m_inProgress
+ || serverRole != DeviceRole.SERVER_ISCLIENT
|| players[ii].isLocal ) {
m_visiblePlayers[m_nVisiblePlayers++] = ii;
}