diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java
index f518bf871..61a06b965 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java
@@ -46,6 +46,7 @@ import android.widget.TextView;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1862,14 +1863,15 @@ public class BoardDelegate extends DelegateBase
}
@Override
- public void informWordBlocked( final String word, final String dict )
+ public void informWordsBlocked( int nWords, final String words, final String dict )
{
runOnUiThread( new Runnable() {
@Override
public void run() {
+ String fmtd = TextUtils.join( ", ", wordsToArray( words ) );
String msg = LocUtils
.getString( m_activity, R.string.word_blocked_by_phony,
- word, dict );
+ fmtd, dict );
makeOkOnlyBuilder( msg ).show();
}
} );
@@ -2922,11 +2924,13 @@ public class BoardDelegate extends DelegateBase
private String[] wordsToArray( String words )
{
String[] tmp = TextUtils.split( words, "\n" );
- String[] wordsArray = new String[tmp.length];
- for ( int ii = 0, jj = tmp.length; ii < tmp.length; ++ii, --jj ) {
- wordsArray[ii] = tmp[jj-1];
+ List list = new ArrayList<>();
+ for ( String one : tmp ) {
+ if ( 0 < one.length() ) {
+ list.add( one );
+ }
}
- return wordsArray;
+ return list.toArray( new String[list.size()] );
}
private boolean inArchiveGroup()
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java
index 49b600aca..ef8784c2e 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java
@@ -62,7 +62,7 @@ public interface UtilCtxt {
void remSelected();
void timerSelected( boolean inDuplicateMode, boolean canPause );
void setIsServer( boolean isServer );
- void informWordBlocked( String word, String dict );
+ void informWordsBlocked( int nWords, String words, String dict );
void bonusSquareHeld( int bonus );
void playerScoreHeld( int player );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
index cfd32dbbf..97666e2d9 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
@@ -107,9 +107,9 @@ public class UtilCtxtImpl implements UtilCtxt {
}
@Override
- public void informWordBlocked( String word, String dict )
+ public void informWordsBlocked( int nWords, String words, String dict )
{
- subclassOverride( "informWordBlocked" );
+ subclassOverride( "informWordsBlocked" );
}
@Override
diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml
index 9a1f15e30..ec5d2fbed 100644
--- a/xwords4/android/app/src/main/res/values/strings.xml
+++ b/xwords4/android/app/src/main/res/values/strings.xml
@@ -2528,7 +2528,7 @@
another crash. Do you want to open it anyway?
Open anyway
- Word %1$s not found in wordlist %2$s.
+ Word or words not found in wordlist %2$s: %1$s.
Debug logs
diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c
index 44af18e23..b9d7da6e1 100644
--- a/xwords4/android/jni/utilwrapper.c
+++ b/xwords4/android/jni/utilwrapper.c
@@ -707,13 +707,14 @@ and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer )
}
static void
-and_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word, const XP_UCHAR* dict )
+and_util_informWordsBlocked( XW_UtilCtxt* uc, XP_U16 nBadWords,
+ XWStreamCtxt* words, const XP_UCHAR* dict )
{
- UTIL_CBK_HEADER( "informWordBlocked", "(Ljava/lang/String;Ljava/lang/String;)V" );
- jstring jword = (*env)->NewStringUTF( env, word );
+ UTIL_CBK_HEADER( "informWordsBlocked", "(ILjava/lang/String;Ljava/lang/String;)V" );
+ jstring jwords = streamToJString( env, words );
jstring jdict = (*env)->NewStringUTF( env, dict );
- (*env)->CallVoidMethod( env, util->jutil, mid, jword, jdict );
- deleteLocalRefs( env, jword, DELETE_NO_REF );
+ (*env)->CallVoidMethod( env, util->jutil, mid, nBadWords, jwords, jdict );
+ deleteLocalRefs( env, jwords, jdict, DELETE_NO_REF );
UTIL_CBK_TAIL();
}
@@ -919,7 +920,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
#endif
SET_PROC(getDevUtilCtxt);
- SET_PROC(informWordBlocked);
+ SET_PROC(informWordsBlocked);
#undef SET_PROC
assertTableFull( vtable, sizeof(*vtable), "util" );
diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c
index 29e643117..496c1472e 100644
--- a/xwords4/common/mscore.c
+++ b/xwords4/common/mscore.c
@@ -1,6 +1,6 @@
/* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */
/*
- * Copyright 1998 - 2011 by Eric House (xwords@eehouse.org). All rights
+ * Copyright 1998 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@@ -218,8 +218,11 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
} /* model_figureFinalScores */
typedef struct _BlockCheckState {
+ ModelCtxt* model;
+ XWStreamCtxt* stream;
WordNotifierInfo* chainNI;
- XP_UCHAR word[32];
+ XP_U16 nBadWords;
+ XP_Bool silent;
} BlockCheckState;
static void
@@ -230,8 +233,17 @@ blockCheck( const WNParams* wnp, void* closure )
if ( !!bcs->chainNI ) {
(bcs->chainNI->proc)( wnp, bcs->chainNI->closure );
}
- if ( !wnp->isLegal && '\0' == bcs->word[0] ) {
- XP_STRCAT( bcs->word, wnp->word );
+ if ( !wnp->isLegal ) {
+ ++bcs->nBadWords;
+ if ( !bcs->silent ) {
+ if ( NULL == bcs->stream ) {
+ bcs->stream =
+ mem_stream_make_raw( MPPARM(bcs->model->vol.mpool)
+ dutil_getVTManager(bcs->model->vol.dutil));
+ }
+ stream_catString( bcs->stream, wnp->word );
+ stream_putU8( bcs->stream, '\n' );
+ }
}
}
@@ -272,7 +284,9 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine,
BlockCheckState bcs;
if ( checkDict ) {
XP_MEMSET( &bcs, 0, sizeof(bcs) );
+ bcs.model = model;
bcs.chainNI = notifyInfo;
+ bcs.silent = silent;
blockWNI.proc = blockCheck;
blockWNI.closure = &bcs;
notifyInfo = &blockWNI;
@@ -280,10 +294,13 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine,
XP_S16 tmpScore = figureMoveScore( model, turn, &moveInfo,
engine, stream, notifyInfo );
- if ( checkDict && '\0' != bcs.word[0] ) {
+ if ( checkDict && 0 < bcs.nBadWords ) {
if ( !silent ) {
+ XP_ASSERT( !!bcs.stream );
DictionaryCtxt* dict = model_getPlayerDict( model, turn );
- util_informWordBlocked( model->vol.util, bcs.word, dict_getName( dict ) );
+ util_informWordsBlocked( model->vol.util, bcs.nBadWords,
+ bcs.stream, dict_getName( dict ) );
+ stream_destroy( bcs.stream );
}
} else {
score = tmpScore;
diff --git a/xwords4/common/util.h b/xwords4/common/util.h
index a4b7b9cfd..73d086c8e 100644
--- a/xwords4/common/util.h
+++ b/xwords4/common/util.h
@@ -174,8 +174,8 @@ typedef struct UtilVtable {
void (*m_util_setIsServer)(XW_UtilCtxt* uc, XP_Bool isServer );
#endif
- void (*m_util_informWordBlocked)( XW_UtilCtxt* uc, const XP_UCHAR* word,
- const XP_UCHAR* dictName );
+ void (*m_util_informWordsBlocked)( XW_UtilCtxt* uc, XP_U16 nBadWords,
+ XWStreamCtxt* words, const XP_UCHAR* dictName );
#ifdef XWFEATURE_SEARCHLIMIT
XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc,
@@ -312,8 +312,8 @@ struct XW_UtilCtxt {
# define util_addrChange( uc, addro, addrn )
#endif
-#define util_informWordBlocked(uc, w, d) \
- (uc)->vtable->m_util_informWordBlocked( (uc), (w), (d) )
+#define util_informWordsBlocked(uc, c, w, d) \
+ (uc)->vtable->m_util_informWordsBlocked( (uc), (c), (w), (d) )
#ifdef XWFEATURE_SEARCHLIMIT
#define util_getTraySearchLimits(uc,min,max) \
diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c
index 496236c24..491a3de9e 100644
--- a/xwords4/linux/cursesboard.c
+++ b/xwords4/linux/cursesboard.c
@@ -1064,11 +1064,12 @@ curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
#endif
static void
-curses_util_informWordBlocked( XW_UtilCtxt* XP_UNUSED(uc),
- const XP_UCHAR* XP_UNUSED_DBG(word),
- const XP_UCHAR* XP_UNUSED_DBG(dict) )
+curses_util_informWordsBlocked( XW_UtilCtxt* XP_UNUSED(uc),
+ XP_U16 XP_UNUSED_DBG(nBadWords),
+ XWStreamCtxt* XP_UNUSED(words),
+ const XP_UCHAR* XP_UNUSED_DBG(dictName) )
{
- XP_LOGFF( "(word=%s, dict=%s)", word, dict );
+ XP_LOGFF( "(nBadWords=%d, dict=%s)", nBadWords, dictName );
}
#ifndef XWFEATURE_STANDALONE_ONLY
@@ -1141,7 +1142,7 @@ setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util )
#ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld);
#endif
- SET_PROC(informWordBlocked);
+ SET_PROC(informWordsBlocked);
#ifdef XWFEATURE_SEARCHLIMIT
SET_PROC(getTraySearchLimits);
diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c
index b0ee10fab..d73de1b31 100644
--- a/xwords4/linux/gtkboard.c
+++ b/xwords4/linux/gtkboard.c
@@ -1999,10 +1999,15 @@ gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
#endif
static void
-gtk_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word, const XP_UCHAR* dict )
+gtk_util_informWordsBlocked( XW_UtilCtxt* uc, XP_U16 nBadWords,
+ XWStreamCtxt* words, const XP_UCHAR* dict )
{
+ XP_U16 len = stream_getSize( words );
+ XP_UCHAR buf[len];
+ stream_getBytes( words, buf, len );
+ buf[len-1] = '\0'; /* overwrite \n */
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
- gchar* msg = g_strdup_printf( "Word \"%s\" not found in %s", word, dict );
+ gchar* msg = g_strdup_printf( "%d word[s] not found in %s:\n%s", nBadWords, dict, buf );
gtkUserError( globals, msg );
g_free( msg );
}
@@ -2239,7 +2244,7 @@ setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util )
#ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld);
#endif
- SET_PROC(informWordBlocked);
+ SET_PROC(informWordsBlocked);
#undef SET_PROC
assertTableFull( util->vtable, sizeof(*util->vtable), "gtk util" );