diff --git a/xwords4/android/XWords4-dbg/res/values-fr/.gitignore b/xwords4/android/XWords4-dbg/res/values-fr/.gitignore
new file mode 100644
index 000000000..be5186f01
--- /dev/null
+++ b/xwords4/android/XWords4-dbg/res/values-fr/.gitignore
@@ -0,0 +1 @@
+strings.xml
diff --git a/xwords4/android/XWords4-dbg/res/values-nl/.gitignore b/xwords4/android/XWords4-dbg/res/values-nl/.gitignore
new file mode 100644
index 000000000..be5186f01
--- /dev/null
+++ b/xwords4/android/XWords4-dbg/res/values-nl/.gitignore
@@ -0,0 +1 @@
+strings.xml
diff --git a/xwords4/android/XWords4/res/layout/chat.xml b/xwords4/android/XWords4/res/layout/chat.xml
index e1f02e40b..422ccfcd7 100644
--- a/xwords4/android/XWords4/res/layout/chat.xml
+++ b/xwords4/android/XWords4/res/layout/chat.xml
@@ -9,7 +9,8 @@
-
-
-
-
-
-
-
diff --git a/xwords4/android/XWords4/res/menu/chat_menu.xml b/xwords4/android/XWords4/res/menu/chat_menu.xml
index 9ce17dcd1..3dda1c98f 100644
--- a/xwords4/android/XWords4/res/menu/chat_menu.xml
+++ b/xwords4/android/XWords4/res/menu/chat_menu.xml
@@ -1,7 +1,14 @@
diff --git a/xwords4/android/XWords4/res/values-nl/.gitignore b/xwords4/android/XWords4/res/values-nl/.gitignore
new file mode 100644
index 000000000..1e28ff4b4
--- /dev/null
+++ b/xwords4/android/XWords4/res/values-nl/.gitignore
@@ -0,0 +1 @@
+/strings.xml
diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml
index f4c78a325..efea39e97 100644
--- a/xwords4/android/XWords4/res/values/strings.xml
+++ b/xwords4/android/XWords4/res/values/strings.xml
@@ -2562,4 +2562,9 @@
The \"%1$s\" option copies an
invitation URL to the clipboard. Paste it into the app of your
choice and send it to your friend.
+
+ Are you sure you want to delete
+ all chat history for this game?\n\n(This action cannot be
+ undone.)
+
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java
index 2431022f6..ea53dfd80 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java
@@ -906,7 +906,7 @@ public class BoardDelegate extends DelegateBase
break;
case R.id.board_menu_game_resend:
- m_jniThread.handle( JNICmd.CMD_RESEND, true, false );
+ m_jniThread.handle( JNICmd.CMD_RESEND, true, false, true );
break;
case R.id.gamel_menu_checkmoves:
@@ -2109,7 +2109,8 @@ public class BoardDelegate extends DelegateBase
}
if ( 0 < m_connTypes.size() ) {
- m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND, force, true );
+ m_jniThread.handle( JNIThread.JNICmd.CMD_RESEND, force, true,
+ false );
}
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java
index 82d6572eb..b4bdd2b6d 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java
@@ -21,19 +21,19 @@
package org.eehouse.android.xw4;
import android.app.Activity;
+import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.view.View;
-import android.view.Menu;
import android.view.MenuItem;
-import android.view.MenuInflater;
+import android.view.View;
+import android.widget.EditText;
import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
-public class ChatDelegate extends DelegateBase
- implements View.OnClickListener {
+import org.eehouse.android.xw4.DlgDelegate.Action;
+
+public class ChatDelegate extends DelegateBase {
private long m_rowid;
private Activity m_activity;
@@ -65,8 +65,13 @@ public class ChatDelegate extends DelegateBase
}
}
- ((Button)findViewById( R.id.send_button ))
- .setOnClickListener( this );
+ final ScrollView scroll = (ScrollView)findViewById( R.id.scroll );
+ scroll.post(new Runnable() {
+ @Override
+ public void run() {
+ scroll.fullScroll(View.FOCUS_DOWN);
+ }
+ });
String title = getString( R.string.chat_title_fmt,
GameUtils.getName( m_activity, m_rowid ) );
@@ -80,29 +85,48 @@ public class ChatDelegate extends DelegateBase
@Override
public boolean onOptionsItemSelected( MenuItem item )
{
- boolean handled = R.id.chat_menu_clear == item.getItemId();
- if ( handled ) {
- DBUtils.clearChatHistory( m_activity, m_rowid );
- LinearLayout layout =
- (LinearLayout)findViewById( R.id.chat_history );
- layout.removeAllViews();
+ boolean handled = true;
+ switch ( item.getItemId() ) {
+ case R.id.chat_menu_clear:
+ if ( handled ) {
+ showConfirmThen( R.string.confirm_clear_chat, Action.CLEAR_ACTION );
+ }
+ break;
+ case R.id.chat_menu_send:
+ EditText edit = (EditText)findViewById( R.id.chat_edit );
+ String text = edit.getText().toString();
+ if ( null == text || text.length() == 0 ) {
+ setResult( Activity.RESULT_CANCELED );
+ } else {
+ DBUtils.appendChatHistory( m_activity, m_rowid, text, true );
+
+ Intent result = new Intent();
+ result.putExtra( BoardDelegate.INTENT_KEY_CHAT, text );
+ setResult( Activity.RESULT_OK, result );
+ }
+ finish();
+ break;
+ default:
+ handled = false;
+ break;
}
return handled;
}
- public void onClick( View view )
+ @Override
+ public void dlgButtonClicked( Action action, int which, Object[] params )
{
- EditText edit = (EditText)findViewById( R.id.chat_edit );
- String text = edit.getText().toString();
- if ( null == text || text.length() == 0 ) {
- setResult( Activity.RESULT_CANCELED );
- } else {
- DBUtils.appendChatHistory( m_activity, m_rowid, text, true );
-
- Intent result = new Intent();
- result.putExtra( BoardDelegate.INTENT_KEY_CHAT, text );
- setResult( Activity.RESULT_OK, result );
+ switch ( action ) {
+ case CLEAR_ACTION:
+ if ( AlertDialog.BUTTON_POSITIVE == which ) {
+ DBUtils.clearChatHistory( m_activity, m_rowid );
+ LinearLayout layout =
+ (LinearLayout)findViewById( R.id.chat_history );
+ layout.removeAllViews();
+ }
+ break;
+ default:
+ super.dlgButtonClicked( action, which, params );
}
- finish();
}
}
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DelegateBase.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DelegateBase.java
index f278985cb..d61882fa7 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DelegateBase.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DelegateBase.java
@@ -528,10 +528,9 @@ public class DelegateBase implements DlgClickNotify,
m_dlgDelegate.eventOccurred( event, args );
break;
default:
- if ( BuildConfig.DEBUG ) {
- DbgUtils.logf( "DelegateBase.eventOccurred(event=%s) (DROPPED)",
- event.toString() );
- }
+ DbgUtils.logdf( "DelegateBase.eventOccurred(event=%s) (DROPPED)",
+ event.toString() );
+ break;
}
if ( 0 != fmtId ) {
diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java
index f7b41574c..79d0de388 100644
--- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java
+++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java
@@ -523,11 +523,11 @@ public class JNIThread extends Thread {
break;
case CMD_RESEND:
- boolean force = ((Boolean)args[0]).booleanValue();
int nSent =
- XwJNI.comms_resendAll( m_jniGamePtr, force,
+ XwJNI.comms_resendAll( m_jniGamePtr,
+ ((Boolean)args[0]).booleanValue(),
((Boolean)args[1]).booleanValue() );
- if ( force ) {
+ if ( ((Boolean)args[2]).booleanValue() ) {
Message.obtain(m_handler, MSGS_SENT, nSent).sendToTarget();
}
break;
diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c
index b1aa40c62..334c69342 100644
--- a/xwords4/common/engine.c
+++ b/xwords4/common/engine.c
@@ -345,9 +345,6 @@ chooseMove( EngineCtxt* engine, PossibleMove** move )
result = (NULL != chosen) && (chosen->score > 0);
- if ( !result ) {
- engine_reset( engine );
- }
return result;
} /* chooseMove */
@@ -381,7 +378,7 @@ normalizeIQ( EngineCtxt* engine, XP_U16 iq )
XP_Bool
engine_findMove( EngineCtxt* engine, const ModelCtxt* model,
XP_U16 turn, const Tile* tiles,
- XP_U16 nTiles, XP_Bool usePrev,
+ const XP_U16 nTiles, XP_Bool usePrev,
#ifdef XWFEATURE_BONUSALL
XP_U16 allTilesBonus,
#endif
@@ -394,7 +391,9 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model,
XP_Bool result = XP_TRUE;
XP_U16 star_row;
XP_Bool canMove = XP_FALSE;
+ XP_Bool isRetry = XP_FALSE;
+ retry:
engine->nTilesMax = XP_MIN( MAX_TRAY_TILES, nTiles );
#ifdef XWFEATURE_BONUSALL
engine->allTilesBonus = allTilesBonus;
@@ -542,6 +541,22 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model,
newMove->nTiles = 0;
}
+ /* Gross hack alert: there's an elusive bug in move cacheing that means
+ when we move forward or back from the highest-scoring move to the
+ lowest (or vice-versa) no move is found. But the next try succeeds,
+ because an engine_reset clears the state that makes that happen. So as
+ a workaround, try doing that when no moves are found. If none is found
+ for some other reason, e.g. no tiles, at least the search should be
+ quick. */
+ if ( !canMove ) {
+ engine_reset( engine );
+ if ( !isRetry ) {
+ isRetry = XP_TRUE;
+ XP_LOGF( "%s: no moves found so retrying", __func__ );
+ goto retry;
+ }
+ }
+
*canMoveP = canMove;
return result;
} /* engine_findMove */
@@ -1146,23 +1161,24 @@ considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft,
static void
saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove )
{
- XP_S16 mostest = 0;
+ XP_S16 mostest;
XP_S16 cmpVal;
XP_Bool usePrev = engine->usePrev;
XP_Bool foundEmpty = XP_FALSE;
+ MoveIterationData* miData = &engine->miData;
if ( 1 == engine->nMovesToSave ) { /* only saving one */
mostest = 0;
} else {
mostest = -1;
/* we're not interested if we've seen this */
- cmpVal = CMPMOVES( posmove, &engine->miData.lastSeenMove );
+ cmpVal = CMPMOVES( posmove, &miData->lastSeenMove );
if ( !usePrev && cmpVal >= 0 ) {
- /* XP_LOGF( "%s: dropping %d: higher than %d", __func__, */
- /* posmove->score, engine->miData.lastSeenMove.score ); */
+ /* XP_LOGF( "%s: dropping %d: >= %d", __func__, */
+ /* posmove->score, miData->lastSeenMove.score ); */
} else if ( usePrev && cmpVal <= 0 ) {
- /* XP_LOGF( "%s: dropping %d: lower than %d", __func__, */
- /* posmove->score, engine->miData.lastSeenMove.score ); */
+ /* XP_LOGF( "%s: dropping %d: <= %d", __func__, */
+ /* posmove->score, miData->lastSeenMove.score ); */
} else {
XP_S16 ii;
/* terminate i at 1 because mostest starts at 0 */
@@ -1178,19 +1194,19 @@ saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove )
/* 1/20/2001 I don't see that this assertion is valid. I
simply don't understand why it isn't tripped all the time
in the old crosswords. */
- /* XP_ASSERT( (engine->miData.lastSeenMove.score == 0x7fff) */
- /* || (engine->miData.savedMoves[i].score */
+ /* XP_ASSERT( (miData->lastSeenMove.score == 0x7fff) */
+ /* || (miData->savedMoves[i].score */
/* <= posmove->score) ); */
- if ( 0 == engine->miData.savedMoves[ii].score ) {
+ if ( 0 == miData->savedMoves[ii].score ) {
foundEmpty = XP_TRUE;
mostest = ii;
break;
} else if ( -1 == mostest ) {
mostest = ii;
} else {
- cmpVal = CMPMOVES( &engine->miData.savedMoves[mostest],
- &engine->miData.savedMoves[ii] );
+ cmpVal = CMPMOVES( &miData->savedMoves[mostest],
+ &miData->savedMoves[ii] );
if ( !usePrev && cmpVal > 0 ) {
mostest = ii;
} else if ( usePrev && cmpVal < 0 ) {
@@ -1204,14 +1220,14 @@ saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove )
while ( mostest >= 0 ) { /* while: so we can break */
/* record the score we're dumping. No point in considering any scores
lower than this for the rest of this round. */
- /* engine->miData.lowestSavedScore = */
- /* engine->miData.savedMoves[lowest].score; */
+ /* miData->lowestSavedScore = */
+ /* miData->savedMoves[lowest].score; */
/* XP_DEBUGF( "lowestSavedScore now %d\n", */
- /* engine->miData.lowestSavedScore ); */
+ /* miData->lowestSavedScore ); */
if ( foundEmpty ) {
/* we're good */
} else {
- cmpVal = CMPMOVES( posmove, &engine->miData.savedMoves[mostest]);
+ cmpVal = CMPMOVES( posmove, &miData->savedMoves[mostest]);
if ( !usePrev && cmpVal <= 0 ) {
break;
} else if ( usePrev && cmpVal >= 0 ) {
@@ -1219,10 +1235,10 @@ saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove )
}
}
/* XP_LOGF( "saving move with score %d at %d (replacing %d)\n", */
- /* posmove->score, mostest, */
- /* engine->miData.savedMoves[mostest].score ); */
- XP_MEMCPY( &engine->miData.savedMoves[mostest], posmove,
- sizeof(engine->miData.savedMoves[mostest]) );
+ /* posmove->score, mostest, */
+ /* miData->savedMoves[mostest].score ); */
+ XP_MEMCPY( &miData->savedMoves[mostest], posmove,
+ sizeof(miData->savedMoves[mostest]) );
break;
}
} /* saveMoveIfQualifies */
@@ -1230,15 +1246,16 @@ saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove )
static void
set_search_limits( EngineCtxt* engine )
{
+ MoveIterationData* miData = &engine->miData;
/* If we're going to be searching backwards we want our highest cached
move as the limit; otherwise the lowest */
- if ( 0 < engine->miData.nInMoveCache ) {
+ if ( 0 < miData->nInMoveCache ) {
XP_U16 srcIndx = engine->usePrev
- ? engine->nMovesToSave-1 : engine->miData.bottom;
- XP_MEMCPY( &engine->miData.lastSeenMove,
- &engine->miData.savedMoves[srcIndx],
- sizeof(engine->miData.lastSeenMove) );
- //engine->miData.lowestSavedScore = 0;
+ ? engine->nMovesToSave-1 : miData->bottom;
+ XP_MEMCPY( &miData->lastSeenMove,
+ &miData->savedMoves[srcIndx],
+ sizeof(miData->lastSeenMove) );
+ //miData->lowestSavedScore = 0;
} else {
/* we're doing this for first time */
engine_reset( engine );
@@ -1249,41 +1266,40 @@ static void
init_move_cache( EngineCtxt* engine )
{
XP_U16 nInMoveCache = engine->nMovesToSave;
+ MoveIterationData* miData = &engine->miData;
XP_U16 ii;
XP_ASSERT( engine->nMovesToSave == NUM_SAVED_ENGINE_MOVES );
for ( ii = 0; ii < NUM_SAVED_ENGINE_MOVES; ++ii ) {
- if ( 0 == engine->miData.savedMoves[ii].score ) {
+ if ( 0 == miData->savedMoves[ii].score ) {
--nInMoveCache;
} else {
break;
}
}
- engine->miData.nInMoveCache = nInMoveCache;
- engine->miData.bottom = NUM_SAVED_ENGINE_MOVES - nInMoveCache;
+ miData->nInMoveCache = nInMoveCache;
+ miData->bottom = NUM_SAVED_ENGINE_MOVES - nInMoveCache;
- if ( engine->usePrev ) {
- engine->miData.curCacheIndex =
- NUM_SAVED_ENGINE_MOVES - nInMoveCache - 1;
- } else {
- engine->miData.curCacheIndex = NUM_SAVED_ENGINE_MOVES;
- }
+ miData->curCacheIndex = engine->usePrev
+ ? NUM_SAVED_ENGINE_MOVES - nInMoveCache - 1
+ : NUM_SAVED_ENGINE_MOVES;
}
static PossibleMove*
next_from_cache( EngineCtxt* engine )
{
+ MoveIterationData* miData = &engine->miData;
PossibleMove* move;
if ( move_cache_empty( engine ) ) {
move = NULL;
} else {
if ( engine->usePrev ) {
- ++engine->miData.curCacheIndex;
+ ++miData->curCacheIndex;
} else {
- --engine->miData.curCacheIndex;
+ --miData->curCacheIndex;
}
- move = &engine->miData.savedMoves[engine->miData.curCacheIndex];
+ move = &miData->savedMoves[miData->curCacheIndex];
}
return move;
}
@@ -1309,21 +1325,22 @@ scoreQualifies( EngineCtxt* engine, XP_U16 score )
{
XP_Bool qualifies = XP_FALSE;
XP_Bool usePrev = engine->usePrev;
+ MoveIterationData* miData = &engine->miData;
- if ( usePrev && score < engine->miData.lastSeenMove.score ) {
+ if ( usePrev && score < miData->lastSeenMove.score ) {
/* drop it */
- } else if ( !usePrev && score > engine->miData.lastSeenMove.score
- /* || (score < engine->miData.lowestSavedScore) */ ) {
+ } else if ( !usePrev && score > miData->lastSeenMove.score
+ /* || (score < miData->lowestSavedScore) */ ) {
/* drop it */
} else {
XP_S16 ii;
- PossibleMove* savedMoves = engine->miData.savedMoves;
+ PossibleMove* savedMoves = miData->savedMoves;
/* Look at each saved score, and return true as soon as one's found
with a lower or equal score to this. As an optimization,
consider remembering what the lowest score is *once there are
NUM_SAVED_ENGINE_MOVES moves in here* and doing a quick test on
that. Or better, keeping the list in sorted order. */
- for ( ii = 0, savedMoves = engine->miData.savedMoves;
+ for ( ii = 0, savedMoves = miData->savedMoves;
ii < engine->nMovesToSave; ++ii, ++savedMoves ) {
if ( savedMoves->score == 0 ) { /* empty slot */
qualifies = XP_TRUE;
diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c
index 4a98eacae..3a538baaf 100644
--- a/xwords4/linux/cursesmain.c
+++ b/xwords4/linux/cursesmain.c
@@ -2119,7 +2119,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
#ifndef XWFEATURE_STANDALONE_ONLY
/* send any events that need to get off before the event loop begins */
- if ( !isServer ) {
+ if ( !!cGlobals->game.comms && !isServer ) {
(void)server_initClientConnection( cGlobals->game.server,
mem_stream_make( MEMPOOL
params->vtMgr,