do wordlist lookups using tiles not chars

Fixing a problem with languages (like Hungarian) where it's legal to use
a two-letter tile or two single-letter tiles to play the same word. When
words are seen, or searched for, as char-arrays, there are
duplicates. Current code crashes, but there's also risk the user gets
unexpected behavior. Crash is fixed, and foundation laid for better UX,
by doing all searches for tile arrays. If a search string translates to
more than one tile array the user must choose. For that choice to make
sense it's now possible to translate tile[] to char[] with a delimiter
between the tile strings.
This commit is contained in:
Eric House 2020-04-28 13:29:12 -07:00
parent f8aa204842
commit b2bc4e34cf
13 changed files with 310 additions and 170 deletions

View file

@ -21,6 +21,8 @@
package org.eehouse.android.xw4;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@ -96,7 +98,7 @@ public class DictBrowseDelegate extends DelegateBase
{
TextView text = (TextView)
inflate( android.R.layout.simple_list_item_1 );
String str = XwJNI.dict_iter_nthWord( m_dictClosure, position );
String str = XwJNI.dict_iter_nthWord( m_dictClosure, position, null );
if ( null != str ) {
text.setText( str );
text.setOnClickListener( DictBrowseDelegate.this );
@ -136,6 +138,7 @@ public class DictBrowseDelegate extends DelegateBase
return section;
}
@Override
public Object[] getSections()
{
m_prefixes = XwJNI.dict_iter_getPrefixes( m_dictClosure );
@ -243,7 +246,7 @@ public class DictBrowseDelegate extends DelegateBase
setFindText( m_browseState.m_prefix );
}
@Override
protected void onDestroy()
{
XwJNI.dict_iter_destroy( m_dictClosure );
@ -254,6 +257,7 @@ public class DictBrowseDelegate extends DelegateBase
@Override
public void finalize()
{
Assert.assertTrueNR( m_dictClosure == 0 );
XwJNI.dict_iter_destroy( m_dictClosure );
try {
super.finalize();
@ -262,6 +266,47 @@ public class DictBrowseDelegate extends DelegateBase
}
}
@Override
protected Dialog makeDialog( DBAlert alert, Object[] params )
{
Dialog dialog = null;
DlgID dlgID = alert.getDlgID();
switch ( dlgID ) {
case CHOOSE_TILES:
final byte[][] choices = (byte[][])params[0];
final String[] strs = new String[choices.length];
for ( int ii = 0; ii < choices.length; ++ii ) {
strs[ii] = XwJNI.dict_iter_tilesToStr( m_dictClosure, choices[ii], "." );
}
final int[] chosen = {0};
dialog = makeAlertBuilder()
.setSingleChoiceItems( strs, chosen[0], new DialogInterface.OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which )
{
chosen[0] = which;
}
} )
.setPositiveButton( android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which )
{
if ( 0 <= chosen[0] ) {
showPrefix( choices[chosen[0]] );
}
}
} )
.setTitle( R.string.pick_tiles_title )
.create();
break;
default:
dialog = super.makeDialog( alert, params );
break;
}
return dialog;
}
//////////////////////////////////////////////////
// View.OnClickListener interface
//////////////////////////////////////////////////
@ -328,7 +373,16 @@ public class DictBrowseDelegate extends DelegateBase
String text = getFindText();
if ( null != text && 0 < text.length() ) {
m_browseState.m_prefix = text;
showPrefix();
byte[][] choices = XwJNI.dict_iter_strToTiles( m_dictClosure, text );
if ( null == choices || 0 == choices.length ) {
String msg = getString( R.string.no_tiles_exist, text, m_name );
makeOkOnlyBuilder( msg ).show();
} else if ( 1 == choices.length ) {
showPrefix( choices[0] );
} else {
showDialogFragment( DlgID.CHOOSE_TILES, (Object)choices );
}
}
}
@ -344,14 +398,15 @@ public class DictBrowseDelegate extends DelegateBase
edit.setText( text );
}
private void showPrefix()
private void showPrefix( byte[] prefix )
{
String text = m_browseState.m_prefix;
if ( null != text && 0 < text.length() ) {
int pos = XwJNI.dict_iter_getStartsWith( m_dictClosure, text );
if ( null != prefix && 0 < prefix.length ) {
// Here's the search
int pos = XwJNI.dict_iter_getStartsWith( m_dictClosure, prefix );
if ( 0 <= pos ) {
m_list.setSelection( pos );
} else {
String text = XwJNI.dict_iter_tilesToStr( m_dictClosure, prefix, null );
DbgUtils.showf( m_activity, R.string.dict_browse_nowords_fmt,
m_name, text );
}

View file

@ -68,6 +68,7 @@ public enum DlgID {
, CHANGE_CONN
, GAMES_LIST_NAME_REMATCH
, ASK_DUP_PAUSE
, CHOOSE_TILES
;
private boolean m_addToStack;

View file

@ -524,12 +524,15 @@ public class XwJNI {
public static native void dict_iter_destroy( long closure );
public static native int dict_iter_wordCount( long closure );
public static native int[] dict_iter_getCounts( long closure );
public static native String dict_iter_nthWord( long closure, int nn );
public static native String dict_iter_nthWord( long closure, int nn, String delim );
public static native String[] dict_iter_getPrefixes( long closure );
public static native int[] dict_iter_getIndices( long closure );
public static native byte[][] dict_iter_strToTiles( long closure,
String str );
public static native int dict_iter_getStartsWith( long closure,
String prefix );
byte[] prefix );
public static native String dict_iter_getDesc( long closure );
public static native String dict_iter_tilesToStr( long closure, byte[] tiles, String delim );
// Private methods -- called only here
private static native long initGlobals( DUtilCtxt dutil, JNIUtils jniu );

View file

@ -2548,4 +2548,7 @@
<!-- Debug-build-only question asked to confirm deletion of saved logs -->
<string name="logstore_clear_confirm">Are you sure you want to
erase your debug logs? This action cannot be undone.</string>
<string name="pick_tiles_title">Pick a tile \"spelling\"</string>
<string name="no_tiles_exist">\"%1$s\" cannot be spelled with tiles in %2$s.</string>
</resources>

View file

@ -2397,7 +2397,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1sendChat
////////////////////////////////////////////////////////////
typedef struct _DictIterData {
JNIEnv* env;
JNIEnv* env; /* GET RID OF THIS!!!! */
JNIGlobalState* globalState;
JNIUtilCtxt* jniutil;
VTableMgr* vtMgr;
@ -2519,7 +2519,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1getPrefixes
XP_UCHAR buf[16];
(void)dict_tilesToString( data->dict,
&data->idata.prefixes[depth*ii],
depth, buf, VSIZE(buf) );
depth, buf, VSIZE(buf), NULL );
jstring jstr = (*env)->NewStringUTF( env, buf );
(*env)->SetObjectArrayElement( env, result, ii, jstr );
deleteLocalRef( env, jstr );
@ -2546,33 +2546,110 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1getIndices
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1nthWord
( JNIEnv* env, jclass C, jlong closure, jint nn)
( JNIEnv* env, jclass C, jlong closure, jint nn, jstring jdelim )
{
jstring result = NULL;
DictIterData* data = (DictIterData*)closure;
if ( NULL != data ) {
if ( dict_getNthWord( &data->iter, nn, data->depth, &data->idata ) ) {
XP_UCHAR buf[64];
dict_wordToString( &data->iter, buf, VSIZE(buf) );
const XP_UCHAR* delim = NULL == jdelim ? NULL
: (*env)->GetStringUTFChars( env, jdelim, NULL );
dict_wordToString( &data->iter, buf, VSIZE(buf), delim );
result = (*env)->NewStringUTF( env, buf );
if ( !!delim ) {
(*env)->ReleaseStringUTFChars( env, jdelim, delim );
}
}
}
return result;
}
typedef struct _FTData {
JNIEnv* env;
jbyteArray arrays[16];
int nArrays;
} FTData;
static XP_Bool
onFoundTiles( void* closure, const Tile* tiles, int nTiles )
{
FTData* ftd = (FTData*)closure;
ftd->arrays[ftd->nArrays++] = makeByteArray( ftd->env, nTiles,
(const jbyte*)tiles );
return ftd->nArrays < VSIZE(ftd->arrays); /* still have room? */
}
JNIEXPORT jobjectArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1strToTiles
( JNIEnv* env, jclass C, jlong closure, jstring jstr )
{
jobjectArray result = NULL;
DictIterData* data = (DictIterData*)closure;
const char* str = (*env)->GetStringUTFChars( env, jstr, NULL );
FTData ftd = { .env = env, };
dict_tilesForString( data->dict, str, onFoundTiles, &ftd );
if ( ftd.nArrays > 0 ) {
result = makeByteArrayArray( env, ftd.nArrays );
for ( int ii = 0; ii < ftd.nArrays; ++ii ) {
(*env)->SetObjectArrayElement( env, result, ii, ftd.arrays[ii] );
deleteLocalRef( env, ftd.arrays[ii] );
}
}
(*env)->ReleaseStringUTFChars( env, jstr, str );
return result;
}
JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1getStartsWith
( JNIEnv* env, jclass C, jlong closure, jstring jprefix )
( JNIEnv* env, jclass C, jlong closure, jbyteArray jtiles )
{
jint result = -1;
DictIterData* data = (DictIterData*)closure;
if ( NULL != data ) {
const char* prefix = (*env)->GetStringUTFChars( env, jprefix, NULL );
if ( 0 <= dict_findStartsWith( &data->iter, prefix ) ) {
result = dict_getPosition( &data->iter );
}
(*env)->ReleaseStringUTFChars( env, jprefix, prefix );
XP_U16 nTiles = (*env)->GetArrayLength( env, jtiles );
jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL );
if ( 0 <= dict_findStartsWith( &data->iter, (Tile*)tiles, nTiles ) ) {
result = dict_getPosition( &data->iter );
}
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 );
return result;
}
JNIEXPORT jstring JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1tilesToStr
( JNIEnv* env, jclass C, jlong closure, jbyteArray jtiles, jstring jdelim )
{
jstring result = NULL;
XP_UCHAR buf[64];
const XP_UCHAR* delim = NULL;
if ( !!jdelim ) {
delim = (*env)->GetStringUTFChars( env, jdelim, NULL );
}
DictIterData* data = (DictIterData*)closure;
XP_U16 nTiles = (*env)->GetArrayLength( env, jtiles );
jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL );
XP_U16 strLen = dict_tilesToString( data->dict, (Tile*)tiles, nTiles,
buf, VSIZE(buf), delim );
if ( 0 < strLen ) {
buf[strLen] = '\0';
result = (*env)->NewStringUTF( env, buf );
}
if ( !!jdelim ) {
(*env)->ReleaseStringUTFChars( env, jdelim, delim );
}
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 );
return result;
}

View file

@ -3726,6 +3726,22 @@ moveTileToBoard( BoardCtxt* board, XWEnv xwe, XP_U16 col, XP_U16 row,
} /* moveTileToBoard */
#ifdef KEY_SUPPORT
typedef struct _FTData {
Tile tile;
XP_Bool found;
} FTData;
static XP_Bool
foundTiles( void* closure, const Tile* tiles, int len )
{
XP_ASSERT( 1 == len );
FTData* ftp = (FTData*)closure;
ftp->tile = tiles[0];
ftp->found = XP_TRUE;
return XP_FALSE;
}
/* Return number between 0 and MAX_TRAY_TILES-1 for valid index, < 0 otherwise */
static XP_S16
keyToIndex( BoardCtxt* board, XP_Key key, Tile* blankFace )
@ -3743,19 +3759,19 @@ keyToIndex( BoardCtxt* board, XP_Key key, Tile* blankFace )
if ( tileIndex < 0 ) {
DictionaryCtxt* dict = model_getDictionary( model );
Tile tile;
XP_UCHAR buf[2] = { key, '\0' };
/* Figure out if we have the tile in the tray */
XP_U16 nTiles = 1;
if ( dict_tilesForString( dict, buf, &tile, &nTiles ) ) { /* in dict? */
FTData ftd = {0};
dict_tilesForString( dict, buf, foundTiles, &ftd );
if ( ftd.found ) {
XP_S16 turn = board->selPlayer;
tileIndex = model_trayContains( model, turn, tile );
tileIndex = model_trayContains( model, turn, ftd.tile );
if ( tileIndex < 0 ) {
Tile blankTile = dict_getBlankTile( dict );
tileIndex = model_trayContains( model, turn, blankTile );
if ( tileIndex >= 0 && !!blankFace ) { /* there's a blank for it */
*blankFace = tile;
*blankFace = ftd.tile;
}
}
}

View file

@ -189,57 +189,6 @@ findStartsWithTiles( DictIter* iter, const Tile* tiles, XP_U16 nTiles )
return 0 == nTiles;
}
static XP_S16
findStartsWithChars( DictIter* iter, const XP_UCHAR* chars, XP_U16 charsOffset,
array_edge* edge, XP_U16 nTilesUsed )
{
XP_S16 result = -1;
XP_U16 charsLen = XP_STRLEN( &chars[charsOffset] );
if ( NULL == edge ) {
if ( 0 == charsLen ) {
iter->nEdges = nTilesUsed;
result = charsOffset;
}
} else if ( 0 == charsLen ) {
iter->nEdges = nTilesUsed;
result = charsOffset;
} else {
const DictionaryCtxt* dict = iter->dict;
XP_U16 nodeSize = dict->nodeSize;
for ( ; ; ) { /* for all the tiles */
Tile tile = EDGETILE( dict, edge );
const XP_UCHAR* facep = NULL;
for ( ; ; ) { /* for each string that tile can be */
facep = dict_getNextTileString( dict, tile, facep );
if ( NULL == facep ) {
break;
}
XP_U16 faceLen = XP_STRLEN( facep );
if ( faceLen > charsLen ) {
faceLen = charsLen;
}
if ( 0 == XP_STRNCMP( facep, &chars[charsOffset], faceLen ) ) {
XP_S16 newOffset =
findStartsWithChars( iter, chars,
charsOffset + faceLen,
dict_follow( dict, edge ),
nTilesUsed + 1 );
if ( result < newOffset ) {
iter->edges[nTilesUsed] = edge;
result = newOffset;
}
break;
}
}
if ( IS_LAST_EDGE( dict, edge ) ) {
break;
}
edge += nodeSize;
}
}
return result;
}
static XP_Bool
startsWith( const DictIter* iter, const Tile* tiles, XP_U16 nTiles )
{
@ -388,7 +337,8 @@ placeWordClose( DictIter* iter, const DictPosition position, XP_U16 depth,
} /* placeWordClose */
static void
iterToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen )
iterToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen,
const XP_UCHAR* delim )
{
XP_U16 ii;
XP_U16 nEdges = iter->nEdges;
@ -396,7 +346,7 @@ iterToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen )
for ( ii = 0; ii < nEdges; ++ii ) {
tiles[ii] = EDGETILE( iter->dict, iter->edges[ii] );
}
(void)dict_tilesToString( iter->dict, tiles, nEdges, buf, buflen );
(void)dict_tilesToString( iter->dict, tiles, nEdges, buf, buflen, delim );
}
#if 0
@ -658,11 +608,10 @@ figurePosition( DictIter* iter )
}
XP_S16
dict_findStartsWith( DictIter* iter, const XP_UCHAR* prefix )
dict_findStartsWith( DictIter* iter, const Tile* prefix, XP_U16 len )
{
ASSERT_INITED( iter );
array_edge* edge = dict_getTopEdge( iter->dict );
XP_S16 offset = findStartsWithChars( iter, prefix, 0, edge, 0 );
XP_S16 offset = findStartsWithTiles( iter, prefix, len );
if ( 0 > offset ) {
/* not found; do nothing */
} else if ( 0 == offset ) {
@ -681,10 +630,11 @@ dict_findStartsWith( DictIter* iter, const XP_UCHAR* prefix )
}
void
dict_wordToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen )
dict_wordToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen,
const XP_UCHAR* delim )
{
ASSERT_INITED( iter );
iterToString( iter, buf, buflen );
iterToString( iter, buf, buflen, delim );
}
DictPosition

View file

@ -73,8 +73,10 @@ XP_Bool dict_getNextWord( DictIter* iter );
XP_Bool dict_getPrevWord( DictIter* iter );
XP_Bool dict_getNthWord( DictIter* iter, DictPosition position, XP_U16 depth,
const IndexData* data );
void dict_wordToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen );
XP_S16 dict_findStartsWith( DictIter* iter, const XP_UCHAR* prefix );
void dict_wordToString( const DictIter* iter, XP_UCHAR* buf, XP_U16 buflen,
const XP_UCHAR* delim );
XP_S16 dict_findStartsWith( DictIter* iter, const Tile* prefix, XP_U16 nTiles );
void dict_stringToTiles( const XP_UCHAR* str, Tile out[], XP_U16* nTiles );
DictPosition dict_getPosition( const DictIter* iter );
#ifdef CPLUS
}

View file

@ -200,40 +200,48 @@ dict_numTileFaces( const DictionaryCtxt* dict )
return dict->nFaces;
} /* dict_numTileFaces */
static void
appendIfSpace( XP_UCHAR** bufp, const XP_UCHAR* end, const XP_UCHAR* newtxt )
{
XP_U16 len = XP_STRLEN( newtxt );
if ( *bufp + len < end ) {
XP_MEMCPY( *bufp, newtxt, len );
*bufp += len;
} else {
*bufp = NULL;
}
}
XP_U16
dict_tilesToString( const DictionaryCtxt* dict, const Tile* tiles,
XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize )
XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize,
const XP_UCHAR* delim )
{
XP_UCHAR* bufp = buf;
XP_UCHAR* end = bufp + bufSize;
XP_U16 result = 0;
const XP_UCHAR* end = bufp + bufSize;
XP_U16 delimLen = NULL == delim ? 0 : XP_STRLEN(delim);
while ( nTiles-- ) {
Tile tile = *tiles++;
for ( int ii = 0; ii < nTiles && !!bufp; ++ii ) {
if ( 0 < delimLen && 0 < ii ) {
appendIfSpace( &bufp, end, delim );
}
Tile tile = tiles[ii];
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
if ( IS_SPECIAL(*facep) ) {
XP_UCHAR* chars = dict->chars[(XP_U16)*facep];
XP_U16 len = XP_STRLEN( chars );
if ( bufp + len >= end ) {
bufp = NULL;
break;
}
XP_MEMCPY( bufp, chars, len );
bufp += len;
appendIfSpace( &bufp, end, chars );
} else {
XP_ASSERT ( tile != dict->blankTile ); /* printing blank should be
handled by specials
mechanism */
if ( bufp + 1 >= end ) {
bufp = NULL;
break;
}
bufp += XP_SNPRINTF( bufp, end - bufp, XP_S, facep );
appendIfSpace( &bufp, end, facep );
}
}
if ( bufp != NULL && bufp < end ) {
XP_U16 result = 0;
if ( !!bufp && bufp < end ) {
*bufp = '\0';
result = bufp - buf;
}
@ -244,46 +252,48 @@ dict_tilesToString( const DictionaryCtxt* dict, const Tile* tiles,
* run out of room in which to return tiles. Failure to match means return of
* XP_FALSE, but if we run out of room before failing we return XP_TRUE.
*/
static XP_S16
static XP_Bool
tilesForStringImpl( const DictionaryCtxt* dict, const XP_UCHAR* str,
Tile* tiles, XP_U16 nTiles, XP_U16 nFound )
Tile* tiles, XP_U16 nTiles, XP_U16 nFound,
OnFoundTiles proc, void* closure )
{
XP_S16 result = -1;
XP_Bool goOn;
if ( nFound == nTiles || '\0' == str[0] ) {
result = nFound;
/* We've recursed to the end and have found a tile! */
goOn = (*proc)( closure, tiles, nFound );
} else {
goOn = XP_TRUE;
XP_U16 nFaces = dict_numTileFaces( dict );
Tile tile;
for ( tile = 0; tile < nFaces; ++tile ) {
for ( Tile tile = 0; goOn && tile < nFaces; ++tile ) {
if ( tile != dict->blankTile ) {
const XP_UCHAR* facep = dict_getTileString( dict, tile );
XP_U16 faceLen = XP_STRLEN( facep );
if ( 0 == XP_STRNCMP( facep, str, faceLen ) ) {
XP_S16 maxFound = tilesForStringImpl( dict, str + faceLen,
tiles, nTiles,
nFound + 1 );
if ( 0 <= maxFound ) {
tiles[nFound] = tile;
result = maxFound;
for ( const XP_UCHAR* facep = NULL; ; ) {
facep = dict_getNextTileString( dict, tile, facep );
if ( !facep ) {
break;
}
XP_U16 faceLen = XP_STRLEN( facep );
if ( 0 == XP_STRNCMP( facep, str, faceLen ) ) {
tiles[nFound] = tile;
goOn = tilesForStringImpl( dict, str + faceLen,
tiles, nTiles, nFound + 1,
proc, closure );
break; /* impossible to have than one match per tile */
}
}
}
}
}
return result;
return goOn;
} /* tilesForStringImpl */
XP_Bool
void
dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* str,
Tile* tiles, XP_U16* nTilesP )
OnFoundTiles proc, void* closure )
{
XP_S16 nFound = tilesForStringImpl( dict, str, tiles, *nTilesP, 0 );
XP_Bool success = 0 <= nFound;
if ( success ) {
*nTilesP = nFound;
}
return success;
Tile tiles[32];
tilesForStringImpl( dict, str, tiles, VSIZE(tiles), 0, proc, closure );
} /* dict_tilesForString */
XP_Bool

View file

@ -1,4 +1,4 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 1997 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
@ -177,7 +177,8 @@ XP_U16 dict_numTiles( const DictionaryCtxt* ctxt, Tile tile );
XP_U16 dict_numTileFaces( const DictionaryCtxt* ctxt );
XP_U16 dict_tilesToString( const DictionaryCtxt* ctxt, const Tile* tiles,
XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize );
XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize,
const XP_UCHAR* delim );
const XP_UCHAR* dict_getTileString( const DictionaryCtxt* ctxt, Tile tile );
const XP_UCHAR* dict_getNextTileString( const DictionaryCtxt* ctxt, Tile tile,
const XP_UCHAR* cur );
@ -186,8 +187,9 @@ const XP_UCHAR* dict_getLangName(const DictionaryCtxt* ctxt );
XP_Bool dict_isUTF8( const DictionaryCtxt* ctxt );
XP_Bool dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* key,
Tile* tiles, XP_U16* nTiles );
typedef XP_Bool (*OnFoundTiles)(void* closure, const Tile* tiles, int len);
void dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* key,
OnFoundTiles proc, void* closure );
XP_Bool dict_faceIsBitmap( const DictionaryCtxt* dict, Tile tile );
void dict_getFaceBitmaps( const DictionaryCtxt* dict, Tile tile,

View file

@ -2245,7 +2245,7 @@ formatTray( const TrayTileSet* tiles, DictionaryCtxt* dict, XP_UCHAR* buf,
buf[ii] = '\0';
} else {
dict_tilesToString( dict, (Tile*)tiles->tiles, tiles->nTiles,
buf, bufSize );
buf, bufSize, NULL );
}
return buf;

View file

@ -753,7 +753,7 @@ scoreWord( const ModelCtxt* model, XWEnv xwe, XP_U16 turn,
XP_UCHAR buf[(MAX_ROWS*2)+1];
dict_tilesToString( dict, checkWordBuf, len, buf,
sizeof(buf) );
sizeof(buf), NULL );
WNParams wnp = { .word = buf, .isLegal = legal, .dict = dict,
#ifdef XWFEATURE_BOARDWORDS
@ -889,7 +889,7 @@ wordScoreFormatterFinish( WordScoreFormatter* fmtr, Tile* word,
{
XP_UCHAR buf[(MAX_ROWS*2)+1];
XP_U16 len = dict_tilesToString( fmtr->dict, word, fmtr->nTiles,
buf, sizeof(buf) );
buf, sizeof(buf), NULL );
if ( !!stream ) {
stream_putBytes( stream, buf, len );

View file

@ -1912,7 +1912,7 @@ testGetNthWord( const DictionaryCtxt* dict, char** XP_UNUSED_DBG(words),
for ( ii = 0, jj = half; ii < half; ii += interval, jj += interval ) {
if ( dict_getNthWord( &iter, ii, depth, data ) ) {
dict_wordToString( &iter, buf, VSIZE(buf) );
dict_wordToString( &iter, buf, VSIZE(buf), "." );
XP_ASSERT( 0 == strcmp( buf, words[ii] ) );
# ifdef PRINT_ALL
XP_LOGF( "%s: word[%ld]: %s", __func__, ii, buf );
@ -1921,7 +1921,7 @@ testGetNthWord( const DictionaryCtxt* dict, char** XP_UNUSED_DBG(words),
XP_ASSERT( 0 );
}
if ( dict_getNthWord( &iter, jj, depth, data ) ) {
dict_wordToString( &iter, buf, VSIZE(buf) );
dict_wordToString( &iter, buf, VSIZE(buf), "." );
XP_ASSERT( 0 == strcmp( buf, words[jj] ) );
# ifdef PRINT_ALL
XP_LOGF( "%s: word[%ld]: %s", __func__, jj, buf );
@ -1931,6 +1931,43 @@ testGetNthWord( const DictionaryCtxt* dict, char** XP_UNUSED_DBG(words),
}
}
}
typedef struct _FTData {
DictIter* iter;
IndexData* data;
char** words;
gchar* prefix;
XP_U16 depth;
} FTData;
static XP_Bool
onFoundTiles( void* closure, const Tile* tiles, int nTiles )
{
FTData* ftp = (FTData*)closure;
XP_S16 lenMatched = dict_findStartsWith( ftp->iter, tiles, nTiles );
if ( 0 <= lenMatched ) {
XP_UCHAR buf[32];
XP_UCHAR bufPrev[32] = {0};
dict_wordToString( ftp->iter, buf, VSIZE(buf), "." );
/* This doesn't work with synonyms like "L-L" for "L·L" */
// XP_ASSERT( 0 == strncasecmp( buf, prefix, lenMatched ) );
DictPosition pos = dict_getPosition( ftp->iter );
XP_ASSERT( 0 == strcmp( buf, ftp->words[pos] ) );
if ( pos > 0 ) {
if ( !dict_getNthWord( ftp->iter, pos-1, ftp->depth, ftp->data ) ) {
XP_ASSERT( 0 );
}
dict_wordToString( ftp->iter, bufPrev, VSIZE(bufPrev), "." );
XP_ASSERT( 0 == strcmp( bufPrev, ftp->words[pos-1] ) );
}
XP_LOGF( "dict_getStartsWith(%s) => %s (prev=%s)",
ftp->prefix, buf, bufPrev );
} else {
XP_LOGFF( "nothing starts with %s", ftp->prefix );
}
return XP_TRUE;
}
static void
walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
@ -1948,7 +1985,7 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
max = MAX_COLS_DICT;
}
dict_initIter( &iter, dict, min, max );
dict_initIter( &iter, dict, min, max );
LengthsArray lens;
XP_U32 count = dict_countWords( &iter, &lens );
@ -1968,7 +2005,7 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
gotOne = dict_getNextWord( &iter ) ) {
XP_ASSERT( dict_getPosition( &iter ) == jj );
XP_UCHAR buf[64];
dict_wordToString( &iter, buf, VSIZE(buf) );
dict_wordToString( &iter, buf, VSIZE(buf), "." );
# ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif
@ -1984,7 +2021,7 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
++jj, gotOne = dict_getPrevWord( &iter ) ) {
XP_ASSERT( dict_getPosition(&iter) == count-jj-1 );
XP_UCHAR buf[64];
dict_wordToString( &iter, buf, VSIZE(buf) );
dict_wordToString( &iter, buf, VSIZE(buf), "." );
# ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif
@ -2026,20 +2063,20 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
}
XP_ASSERT( word.index == indices[ii] );
XP_UCHAR buf1[64];
dict_wordToString( dict, &word, buf1, VSIZE(buf1) );
dict_wordToString( dict, &word, buf1, VSIZE(buf1), "." );
XP_UCHAR buf2[64] = {0};
if ( ii > 0 && dict_getNthWord( dict, &word, indices[ii]-1 ) ) {
dict_wordToString( dict, &word, buf2, VSIZE(buf2) );
dict_wordToString( dict, &word, buf2, VSIZE(buf2), "." );
}
char prfx[8];
dict_tilesToString( dict, &prefixes[depth*ii], depth, prfx,
VSIZE(prfx) );
VSIZE(prfx), NULL );
fprintf( stderr, "%d: index: %ld; prefix: %s; word: %s (prev: %s)\n",
ii, indices[ii], prfx, buf1, buf2 );
}
#endif
XP_LOGF( "testing getNth WITH INDEXING" );
XP_LOGFF( "testing getNth WITH INDEXING" );
testGetNthWord( dict, words, depth, &data, min, max );
if ( !!testPrefixes ) {
@ -2047,31 +2084,13 @@ walk_dict_test( MPFORMAL const DictionaryCtxt* dict,
guint count = g_slist_length( testPrefixes );
for ( ii = 0; ii < count; ++ii ) {
gchar* prefix = (gchar*)g_slist_nth_data( testPrefixes, ii );
XP_S16 lenMatched = dict_findStartsWith( &iter, prefix );
if ( 0 <= lenMatched ) {
XP_UCHAR buf[32];
XP_UCHAR bufPrev[32] = {0};
dict_wordToString( &iter, buf, VSIZE(buf) );
XP_LOGFF( "prefix %d: %s", ii, prefix );
/* This doesn't work with synonyms like "L-L" for "L·L" */
// XP_ASSERT( 0 == strncasecmp( buf, prefix, lenMatched ) );
DictPosition pos = dict_getPosition( &iter );
XP_ASSERT( 0 == strcmp( buf, words[pos] ) );
if ( pos > 0 ) {
if ( !dict_getNthWord( &iter, pos-1, depth, &data ) ) {
XP_ASSERT( 0 );
}
dict_wordToString( &iter, bufPrev, VSIZE(bufPrev) );
XP_ASSERT( 0 == strcmp( bufPrev, words[pos-1] ) );
}
XP_LOGF( "dict_getStartsWith(%s) => %s (prev=%s)",
prefix, buf, bufPrev );
} else {
XP_LOGF( "nothing starts with %s", prefix );
}
FTData foundTilesData = { .iter = &iter, .words = words,
.depth = depth, .data = &data,
.prefix = prefix, };
dict_tilesForString( dict, prefix, onFoundTiles, &foundTilesData );
}
}
XP_FREE( mpool, data.indices );
XP_FREE( mpool, data.prefixes );
@ -2108,7 +2127,7 @@ dumpDict( DictionaryCtxt* dict )
result;
result = dict_getNextWord( &iter ) ) {
XP_UCHAR buf[32];
dict_wordToString( &iter, buf, VSIZE(buf) );
dict_wordToString( &iter, buf, VSIZE(buf), "." );
fprintf( stdout, "%s\n", buf );
}
}
@ -2594,8 +2613,10 @@ main( int argc, char** argv )
mainParams.dbName = "xwgames.sqldb";
mainParams.cursesListWinHt = 5;
trimDictPath( "./dict.xwd", dictbuf, VSIZE(dictbuf), &path, &dict );
mainParams.pgi.dictName = copyString( mainParams.mpool, dict );
if ( file_exists( "./dict.xwd" ) ) {
trimDictPath( "./dict.xwd", dictbuf, VSIZE(dictbuf), &path, &dict );
mainParams.pgi.dictName = copyString( mainParams.mpool, dict );
}
char* envDictPath = getenv( "XW_DICTDIR" );
XP_LOGFF( "envDictPath=%s", envDictPath );