diff --git a/xwords4/android/XWords4/jni/Android.mk b/xwords4/android/XWords4/jni/Android.mk index 97b046bb8..7787db2c9 100644 --- a/xwords4/android/XWords4/jni/Android.mk +++ b/xwords4/android/XWords4/jni/Android.mk @@ -23,6 +23,7 @@ local_DEFINES += \ -DDROP_BITMAPS \ -DDISABLE_EMPTYTRAY_UNDO \ -DDISABLE_TILE_SEL \ + -DXWFEATURE_BOARDWORDS \ -DNODE_CAN_4 \ -DRELAY_ROOM_DEFAULT=\"\"\ -D__LITTLE_ENDIAN \ diff --git a/xwords4/android/XWords4/jni/utilwrapper.c b/xwords4/android/XWords4/jni/utilwrapper.c index ed0bc12df..a2a1a9cef 100644 --- a/xwords4/android/XWords4/jni/utilwrapper.c +++ b/xwords4/android/XWords4/jni/utilwrapper.c @@ -422,6 +422,21 @@ and_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) } #endif +#ifdef XWFEATURE_BOARDWORDS +static void +and_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) +{ + if ( NULL != words ) { + UTIL_CBK_HEADER( "cellSquareHeld", "(Ljava/lang/String;)V" ); + jstring jwords = + streamToJString( MPPARM(util->util.mpool) env, words ); + (*env)->CallVoidMethod( env, util->jutil, mid, jwords ); + (*env)->DeleteLocalRef( env, jwords ); + UTIL_CBK_TAIL(); + } +} +#endif + #ifndef XWFEATURE_STANDALONE_ONLY static void and_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr, @@ -512,6 +527,10 @@ makeUtil( MPFORMAL JNIEnv** envp, jobject jutil, CurGameInfo* gi, SET_PROC(playerScoreHeld); #endif +#ifdef XWFEATURE_BOARDWORDS + SET_PROC(cellSquareHeld); +#endif + #ifndef XWFEATURE_STANDALONE_ONLY SET_PROC(addrChange); #endif diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index 712e947be..952920ccd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -1,6 +1,6 @@ /* -*- compile-command: "cd ../../../../../; ant install"; -*- */ /* - * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All + * Copyright 2009-2011 by Eric House (xwords@eehouse.org). All * rights reserved. * * This program is free software; you can redistribute it and/or @@ -993,6 +993,16 @@ public class BoardActivity extends XWActivity } ); } + @Override + public void cellSquareHeld( final String words ) + { + post( new Runnable() { + public void run() { + launchLookup( wordsToArray( words ) ); + } + } ); + } + public void setTimer( int why, int when, int handle ) { if ( null != m_timers[why] ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 72efae8c5..646757ce4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -49,6 +49,7 @@ public interface UtilCtxt { void bonusSquareHeld( int bonus ); void playerScoreHeld( int player ); + void cellSquareHeld( String words ); static final int STRD_ROBOT_TRADED = 1; static final int STR_ROBOT_MOVED = 2; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index 5e2aeb418..435df4f70 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -91,6 +91,10 @@ public class UtilCtxtImpl implements UtilCtxt { { } + public void cellSquareHeld( String words ) + { + } + public String getUserString( int stringCode ) { int id = 0; diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 476cc61f9..b279f3fae 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -726,7 +726,12 @@ hideMiniWindow( BoardCtxt* board, XP_Bool destroy, MiniWindowType winType ) #endif static XP_Bool -warnBadWords( const XP_UCHAR* word, XP_Bool isLegal, void* closure ) +warnBadWords( const XP_UCHAR* word, XP_Bool isLegal, +#ifdef XWFEATURE_BOARDWORDS + const MoveInfo* XP_UNUSED(movei), + XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end), +#endif + void* closure ) { XP_Bool ok = XP_TRUE; if ( !isLegal ) { @@ -965,15 +970,37 @@ timerFiredForPen( BoardCtxt* board ) draw = dragDropSetAdd( board ); #endif } else { - XWBonusType bonus; - bonus = util_getSquareBonus( board->util, board->model, - col, row ); - if ( bonus != BONUS_NONE ) { -#ifdef XWFEATURE_MINIWIN - text = draw_getMiniWText( board->draw, (XWMiniTextType)bonus ); -#else - util_bonusSquareHeld( board->util, bonus ); + XP_Bool listWords = XP_FALSE; +#ifdef XWFEATURE_BOARDWORDS + XP_U16 modelCol, modelRow; + flipIf( board, col, row, &modelCol, &modelRow ); + listWords = model_getTile( board->model, modelCol, modelRow, + XP_TRUE, board->selPlayer, NULL, + NULL, NULL, NULL ); + if ( listWords ) { + XWStreamCtxt* stream = + mem_stream_make( MPPARM(board->mpool) + util_getVTManager(board->util), NULL, + CHANNEL_NONE, + (MemStreamCloseCallback)NULL ); + model_listWordsThrough( board->model, modelCol, modelRow, + stream ); + util_cellSquareHeld( board->util, stream ); + stream_destroy( stream ); + } #endif + if ( !listWords ) { + XWBonusType bonus; + bonus = util_getSquareBonus( board->util, board->model, + col, row ); + if ( bonus != BONUS_NONE ) { +#ifdef XWFEATURE_MINIWIN + text = draw_getMiniWText( board->draw, + (XWMiniTextType)bonus ); +#else + util_bonusSquareHeld( board->util, bonus ); +#endif + } } } board->penTimerFired = XP_TRUE; diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 925683786..b39fd06da 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -75,7 +75,11 @@ static void loadPlayerCtxt( XWStreamCtxt* stream, XP_U16 version, PlayerCtxt* pc ); static void writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); static XP_U16 model_getRecentPassCount( ModelCtxt* model ); -static XP_Bool recordWord( const XP_UCHAR* word, XP_Bool isLegal, void* clsur ); +static XP_Bool recordWord( const XP_UCHAR* word, XP_Bool isLegal, +#ifdef XWFEATURE_BOARDWORDS + const MoveInfo* movei, XP_U16 start, XP_U16 end, +#endif + void* clsur ); /***************************************************************************** * @@ -1948,7 +1952,11 @@ typedef struct _FirstWordData { } FirstWordData; static XP_Bool -getFirstWord( const XP_UCHAR* word, XP_Bool isLegal, void* closure ) +getFirstWord( const XP_UCHAR* word, XP_Bool isLegal, +#ifdef XWFEATURE_BOARDWORDS + const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end), +#endif + void* closure ) { LOG_FUNC(); if ( isLegal ) { @@ -2035,16 +2043,26 @@ model_recentPassCountOk( ModelCtxt* model ) return count < okCount; } -static XP_Bool -recordWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), void* closure ) +static void +appendWithCR( XWStreamCtxt* stream, const XP_UCHAR* word, XP_U16* counter ) { - RecordWordsInfo* info = (RecordWordsInfo*)closure; - XWStreamCtxt* stream = info->stream; - XP_LOGF( "%s(%s)", __func__, word ); - if ( 0 < info->nWords++ ) { + if ( 0 < (*counter)++ ) { stream_putU8( stream, '\n' ); } stream_catString( stream, word ); +} + +static XP_Bool +recordWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), +#ifdef XWFEATURE_BOARDWORDS + const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), + XP_U16 XP_UNUSED(end), +#endif + void* closure ) + +{ + RecordWordsInfo* info = (RecordWordsInfo*)closure; + appendWithCR( info->stream, word, &info->nWords ); return XP_TRUE; } @@ -2085,6 +2103,65 @@ model_getWordsPlayed( ModelCtxt* model, XP_U16 nTurns, XWStreamCtxt* stream ) stack_destroy( tmpStack ); } +#ifdef XWFEATURE_BOARDWORDS + +typedef struct _ListWordsThroughInfo { + XWStreamCtxt* stream; + XP_U16 col, row; + XP_U16 nWords; +} ListWordsThroughInfo; + +static XP_Bool +listWordsThrough( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), + const MoveInfo* movei, XP_U16 start, XP_U16 end, + void* closure ) +{ + ListWordsThroughInfo* info = (ListWordsThroughInfo*)closure; + + XP_Bool contained = XP_FALSE; + if ( movei->isHorizontal && movei->commonCoord == info->row ) { + contained = start <= info->col && end >= info->col; + } else if ( !movei->isHorizontal && movei->commonCoord == info->col ) { + contained = start <= info->row && end >= info->row; + } + + if ( contained ) { + appendWithCR( info->stream, word, &info->nWords ); + } + + return XP_TRUE; +} + +/* List every word played that includes the tile on {col,row}. + * + * How? Undo backwards until we find the move that placed that tile.*/ +void +model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, + XWStreamCtxt* stream ) +{ + XP_ASSERT( !!stream ); + StackCtxt* stack = model->vol.stack; + StackCtxt* tmpStack = stack_copy( stack ); + + XP_U16 nPlayers = model->nPlayers; + XP_U16 nEntries = stack_getNEntries( stack ) - nPlayers; /* skip assignments */ + + if ( model_undoLatestMoves( model, NULL, nEntries, NULL, NULL ) ) { + ListWordsThroughInfo lwtInfo = { .stream = stream, .col = col, + .row = row, .nWords = 0, + }; + WordNotifierInfo ni = { .proc = listWordsThrough, .closure = &lwtInfo }; + /* Now push the undone moves back into the model one at a time. + recordWord() will add each played word to the stream as it's + scored */ + buildModelFromStack( model, tmpStack, XP_TRUE, nPlayers, + (XWStreamCtxt*)NULL, &ni, (MovePrintFuncPre)NULL, + (MovePrintFuncPost)NULL, NULL ); + } + stack_destroy( tmpStack ); +} +#endif + XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, XP_UCHAR* expl, XP_U16* explLen ) diff --git a/xwords4/common/model.h b/xwords4/common/model.h index b24b2371f..50b433166 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -239,6 +239,10 @@ void model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts, /********************* scoring ********************/ typedef XP_Bool (*WordNotifierProc)( const XP_UCHAR* word, XP_Bool isLegal, +#ifdef XWFEATURE_BOARDWORDS + const MoveInfo* movei, XP_U16 start, + XP_U16 end, +#endif void* closure ); typedef struct WordNotifierInfo { WordNotifierProc proc; @@ -254,6 +258,10 @@ XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, XP_UCHAR* expl, XP_U16* explLen ); void model_getWordsPlayed( ModelCtxt* model, XP_U16 nTurns, XWStreamCtxt* stream ); +#ifdef XWFEATURE_BOARDWORDS +void model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, + XWStreamCtxt* stream ); +#endif /* Have there been too many passes (so game should end)? */ XP_Bool model_recentPassCountOk( ModelCtxt* model ); diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 6be9b12ae..012cada53 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -40,9 +40,9 @@ static XP_U16 find_start( const ModelCtxt* model, XP_U16 col, XP_U16 row, static XP_S16 checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, XWStreamCtxt* stream, XP_Bool silent, WordNotifierInfo* notifyInfo ); -static XP_U16 scoreWord( const ModelCtxt* model, XP_U16 turn, MoveInfo* movei, - EngineCtxt* engine, XWStreamCtxt* stream, - WordNotifierInfo* notifyInfo ); +static XP_U16 scoreWord( const ModelCtxt* model, XP_U16 turn, + const MoveInfo* movei, EngineCtxt* engine, + XWStreamCtxt* stream, WordNotifierInfo* notifyInfo ); /* for formatting when caller wants an explanation of the score. These live in separate function called only when stream != NULL so that they'll have @@ -553,7 +553,7 @@ tile_multiplier( const ModelCtxt* model, XP_U16 col, XP_U16 row ) static XP_U16 scoreWord( const ModelCtxt* model, XP_U16 turn, - MoveInfo* movei, /* new tiles */ + const MoveInfo* movei, /* new tiles */ EngineCtxt* engine,/* for crosswise caching */ XWStreamCtxt* stream, WordNotifierInfo* notifyInfo ) @@ -567,7 +567,7 @@ scoreWord( const ModelCtxt* model, XP_U16 turn, XP_U16 start, end; XP_U16* incr; XP_U16 col, row; - MoveInfoTile* tiles = movei->tiles; + const MoveInfoTile* tiles = movei->tiles; XP_U16 firstCoord = tiles->varCoord; DictionaryCtxt* dict = model_getPlayerDict( model, turn ); @@ -671,7 +671,11 @@ scoreWord( const ModelCtxt* model, XP_U16 turn, XP_UCHAR buf[(MAX_ROWS*2)+1]; dict_tilesToString( dict, checkWordBuf, len, buf, sizeof(buf) ); - (void)(*notifyInfo->proc)( buf, legal, notifyInfo->closure ); + (void)(*notifyInfo->proc)( buf, legal, +#ifdef XWFEATURE_BOARDWORDS + movei, start, end, +#endif + notifyInfo->closure ); } if ( !!stream ) { diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 24a1ea5fe..14409bdae 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -1734,7 +1734,12 @@ server_setGameOverListener( ServerCtxt* server, GameOverListener gol, } /* server_setGameOverListener */ static XP_Bool -storeBadWords( const XP_UCHAR* word, XP_Bool isLegal, void* closure ) +storeBadWords( const XP_UCHAR* word, XP_Bool isLegal, +#ifdef XWFEATURE_BOARDWORDS + const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), + XP_U16 XP_UNUSED(end), +#endif + void* closure ) { if ( !isLegal ) { ServerCtxt* server = (ServerCtxt*)closure; diff --git a/xwords4/common/util.h b/xwords4/common/util.h index 4ba082f01..b1da52012 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -162,6 +162,9 @@ typedef struct UtilVtable { void (*m_util_bonusSquareHeld)( XW_UtilCtxt* uc, XWBonusType bonus ); void (*m_util_playerScoreHeld)( XW_UtilCtxt* uc, XP_U16 player ); #endif +#ifdef XWFEATURE_BOARDWORDS + void (*m_util_cellSquareHeld)( XW_UtilCtxt* uc, XWStreamCtxt* words ); +#endif #ifndef XWFEATURE_STANDALONE_ONLY void (*m_util_addrChange)( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr, @@ -271,7 +274,10 @@ struct XW_UtilCtxt { # define util_playerScoreHeld( uc, player ) \ (uc)->vtable->m_util_playerScoreHeld( (uc), (player) ) #endif - +#ifdef XWFEATURE_BOARDWORDS +#define util_cellSquareHeld(uc, s) \ + (uc)->vtable->m_util_cellSquareHeld( (uc), (s) ) +#endif #ifndef XWFEATURE_STANDALONE_ONLY # define util_addrChange( uc, addro, addrn ) \ (uc)->vtable->m_util_addrChange((uc), (addro), (addrn)) diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 9be2e4f46..b3dbcbcbd 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -38,6 +38,7 @@ ifdef CURSES_SMALL_SCREEN DO_CURSES += -DCURSES_SMALL_SCREEN endif DO_GTK = -DPLATFORM_GTK +DO_GTK += -DXWFEATURE_BOARDWORDS # DO_GTK += -DUSE_CAIRO # uncomment for standalone build diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index cda18207d..62f9780d2 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -1730,6 +1730,15 @@ gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) } #endif +#ifdef XWFEATURE_BOARDWORDS +static void +gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) +{ + XP_USE( uc ); + XP_USE( words ); +} +#endif + static void gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) { @@ -1952,6 +1961,9 @@ setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld; util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld; #endif +#ifdef XWFEATURE_BOARDWORDS + util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld; +#endif util->closure = globals; } /* setupGtkUtilCallbacks */