When user dismisses GameConfig form, check if the game being viewed

has any state (moves) and if the changes being made would invalidate
that state and force a reset.  If so, give the user a choice between
save and discard.  Means CurGameInfo behaves slightly differently if a
game is in progress.  Also fix game reset to preserve comms address.
This commit is contained in:
eehouse 2010-03-13 23:10:30 +00:00
parent 11d6f7417e
commit 2761772e05
6 changed files with 229 additions and 50 deletions

View file

@ -50,6 +50,8 @@
<string name="button_cancel">Cancel</string>
<string name="button_yes">Yes</string>
<string name="button_no">No</string>
<string name="button_save">Save</string>
<string name="button_discard">Discard</string>
<string name="player_label">Name:</string>
<string name="game_config_open">Open</string>
@ -161,7 +163,13 @@
<string name="download_dicts">Download more...</string>
<string name="dict_url">http://eehouse.org/and_dicts</string>
<!--string name="dict_url">http://10.0.2.2/~eehouse/and_dicts</string-->
<string name="confirm_save_title">Confirm save</string>
<string name="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?</string>
<string name="hints_allowed">Allow hints</string>
<string name="use_timer">Enable timer</string>
<string name="color_tiles">Color tiles</string>

View file

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

View file

@ -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 );

View file

@ -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 )
{

View file

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

View file

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