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; }