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