diff --git a/xwords4/wasm/assets_dir/ic_downarrow.png b/xwords4/wasm/assets_dir/ic_downarrow.png new file mode 100644 index 000000000..557f34818 Binary files /dev/null and b/xwords4/wasm/assets_dir/ic_downarrow.png differ diff --git a/xwords4/wasm/assets_dir/ic_origin.png b/xwords4/wasm/assets_dir/ic_origin.png new file mode 100644 index 000000000..881c5cf72 Binary files /dev/null and b/xwords4/wasm/assets_dir/ic_origin.png differ diff --git a/xwords4/wasm/assets_dir/ic_rightarrow.png b/xwords4/wasm/assets_dir/ic_rightarrow.png new file mode 100644 index 000000000..cae42ee7b Binary files /dev/null and b/xwords4/wasm/assets_dir/ic_rightarrow.png differ diff --git a/xwords4/wasm/main.c b/xwords4/wasm/main.c index 2cc6de9fd..65828b8ce 100644 --- a/xwords4/wasm/main.c +++ b/xwords4/wasm/main.c @@ -36,21 +36,29 @@ EM_JS(bool, call_confirm, (const char* str), { return confirm(UTF8ToString(str)); }); +EM_JS(void, call_alert, (const char* str), { + alert(UTF8ToString(str)); +}); static void initGlobals( Globals* globals ) { globals->cp.showBoardArrow = XP_TRUE; globals->cp.allowPeek = XP_TRUE; + globals->cp.showRobotScores = XP_TRUE; + globals->cp.sortNewTiles = XP_TRUE; globals->gi.serverRole = SERVER_STANDALONE; + globals->gi.phoniesAction = PHONIES_WARN; + globals->gi.nPlayers = 2; globals->gi.boardSize = 15; globals->gi.dictName = "myDict"; - globals->gi.players[0].name = "Eric"; + globals->gi.players[0].name = "You"; globals->gi.players[0].isLocal = XP_TRUE; - globals->gi.players[1].name = "Kati"; + globals->gi.players[1].name = "Robot"; globals->gi.players[1].isLocal = XP_TRUE; + globals->gi.players[1].robotIQ = 99; globals->mpool = mpool_make( "wasm" ); globals->vtMgr = make_vtablemgr( globals->mpool ); @@ -99,20 +107,33 @@ getCurMS() return result; } -static void +static XP_Bool checkForTimers( Globals* globals ) { + XP_Bool draw = XP_FALSE; time_t now = getCurMS(); for ( XWTimerReason why = 0; why < NUM_TIMERS_PLUS_ONE; ++why ) { TimerState* timer = &globals->timers[why]; XWTimerProc proc = timer->proc; if ( !!proc && now >= timer->when ) { timer->proc = NULL; - XP_LOGFF( "timer fired (why=%d): calling proc", why ); (*proc)( timer->closure, NULL, why ); - XP_LOGFF( "back from proc" ); + draw = XP_TRUE; /* just in case */ } } + return draw; +} + +static XP_Bool +checkForIdle( Globals* globals ) +{ + XP_Bool draw = XP_FALSE; + IdleProc proc = globals->idleProc; + if ( !!proc ) { + globals->idleProc = NULL; + draw = (*proc)(globals->idleClosure); + } + return draw; } void @@ -134,7 +155,21 @@ main_query( Globals* globals, const XP_UCHAR* query, QueryProc proc, void* closu (*proc)( closure, ok ); } -static void +void +main_alert( Globals* globals, const XP_UCHAR* msg ) +{ + call_alert( msg ); +} + +void +main_set_idle( Globals* globals, IdleProc proc, void* closure ) +{ + XP_ASSERT( !globals->idleProc ); + globals->idleProc = proc; + globals->idleClosure = closure; +} + +static XP_Bool checkForEvent( Globals* globals ) { XP_Bool handled; @@ -165,20 +200,24 @@ checkForEvent( Globals* globals ) } // XP_LOGFF( "draw: %d", draw ); - if ( draw ) { - SDL_RenderClear( globals->renderer ); - board_draw( globals->game.board, NULL ); - wasm_draw_render( globals->draw, globals->renderer ); - SDL_RenderPresent( globals->renderer ); - } + return draw; } static void looper( void* closure ) { Globals* globals = (Globals*)closure; - checkForTimers( globals ); - checkForEvent( globals ); + XP_Bool draw = checkForTimers( globals ); + draw = checkForIdle( globals ) || draw; + draw = checkForEvent( globals ) || draw; + + if ( draw ) { + SDL_RenderClear( globals->renderer ); + board_draw( globals->game.board, NULL ); + wasm_draw_render( globals->draw, globals->renderer ); + SDL_RenderPresent( globals->renderer ); + } + } int main( int argc, char** argv ) diff --git a/xwords4/wasm/main.h b/xwords4/wasm/main.h index bced9d765..28c612a25 100644 --- a/xwords4/wasm/main.h +++ b/xwords4/wasm/main.h @@ -14,6 +14,7 @@ typedef struct _TimerState { time_t when; } TimerState; +typedef XP_Bool (*IdleProc)(void* closure); typedef struct _Globals { SDL_Window* window; @@ -30,6 +31,9 @@ typedef struct _Globals { TimerState timers[NUM_TIMERS_PLUS_ONE]; + IdleProc idleProc; + void* idleClosure; + MemPoolCtx* mpool; } Globals; @@ -37,9 +41,10 @@ void main_set_timer( Globals* globals, XWTimerReason why, XP_U16 when, XWTimerProc proc, void* closure ); typedef void (*QueryProc)(void* closure, XP_Bool confirmed); - void main_query( Globals* globals, const XP_UCHAR* query, QueryProc proc, void* closure ); +void main_set_idle( Globals* globals, IdleProc proc, void* closure ); +void main_alert( Globals* globals, const XP_UCHAR* msg ); #endif diff --git a/xwords4/wasm/wasmdraw.c b/xwords4/wasm/wasmdraw.c index 0e0d85767..ecf3b0b0f 100644 --- a/xwords4/wasm/wasmdraw.c +++ b/xwords4/wasm/wasmdraw.c @@ -1,5 +1,6 @@ #include #include +#include #include "comtypes.h" #include "wasmdraw.h" @@ -17,6 +18,10 @@ typedef struct _WasmDrawCtx { TTF_Font* font36; TTF_Font* font48; + SDL_Surface* arrowDown; + SDL_Surface* arrowRight; + SDL_Surface* origin; + int trayOwner; } WasmDrawCtx; @@ -116,8 +121,8 @@ textInRect( WasmDrawCtx* wdctx, const XP_UCHAR* text, const XP_Rect* rect, int width, height; SDL_QueryTexture( texture, NULL, NULL, &width, &height ); - XP_LOGFF( "have w: %d; h: %d; got w: %d; h: %d", - tmpR.width, tmpR.height, width, height ); + /* XP_LOGFF( "have w: %d; h: %d; got w: %d; h: %d", */ + /* tmpR.width, tmpR.height, width, height ); */ tmpR.width = XP_MIN( width, tmpR.width ); tmpR.height = XP_MIN( height, tmpR.height ); @@ -127,6 +132,16 @@ textInRect( WasmDrawCtx* wdctx, const XP_UCHAR* text, const XP_Rect* rect, SDL_DestroyTexture( texture ); } +static void +imgInRect( WasmDrawCtx* wdctx, SDL_Surface* img, const XP_Rect* rect ) +{ + SDL_Texture* texture = SDL_CreateTextureFromSurface( wdctx->renderer, img ); + SDL_Rect sdlr; + rectXPToSDL( &sdlr, rect ); + SDL_RenderCopy( wdctx->renderer, texture, NULL, &sdlr ); + SDL_DestroyTexture( texture ); +} + static void drawTile( WasmDrawCtx* wdctx, const XP_UCHAR* face, int val, int owner, const XP_Rect* rect ) @@ -323,6 +338,9 @@ wasm_draw_drawCell( DrawCtx* dctx, XWEnv xwe, const XP_Rect* rect, } else { fillRect( wdctx, rect, sBonusColors[bonus-1] ); } + if ( 0 != (flags & CELL_ISSTAR) ) { + imgInRect( wdctx, wdctx->origin, rect ); + } } else if ( !!text ) { textInRect( wdctx, text, rect, NULL ); } @@ -400,10 +418,11 @@ wasm_draw_drawBoardArrow ( DrawCtx* dctx, XWEnv xwe, XWBonusType bonus, XP_Bool vert, HintAtts hintAtts, CellFlags flags ) { - LOG_FUNC(); WasmDrawCtx* wdctx = (WasmDrawCtx*)dctx; const XP_UCHAR* str = vert ? "|" : "-"; - textInRect( wdctx, str, rect, NULL ); + SDL_Surface* img = vert ? wdctx->arrowDown : wdctx->arrowRight; + + imgInRect( wdctx, img, rect ); } static void @@ -453,6 +472,10 @@ wasm_draw_make( MPFORMAL int width, int height ) wdctx->font36 = TTF_OpenFont( "assets_dir/FreeSans.ttf", 36 ); wdctx->font48 = TTF_OpenFont( "assets_dir/FreeSans.ttf", 48 ); + wdctx->arrowDown = IMG_Load( "assets_dir/ic_downarrow.png" ); + wdctx->arrowRight = IMG_Load( "assets_dir/ic_rightarrow.png" ); + wdctx->origin = IMG_Load( "assets_dir/ic_origin.png" ); + wdctx->vtable = XP_MALLOC( mpool, sizeof(*wdctx->vtable) ); SET_VTABLE_ENTRY( wdctx->vtable, draw_clearRect, wasm ); diff --git a/xwords4/wasm/wasmutil.c b/xwords4/wasm/wasmutil.c index 46df80316..c3c1d32a1 100644 --- a/xwords4/wasm/wasmutil.c +++ b/xwords4/wasm/wasmutil.c @@ -2,6 +2,7 @@ #include "util.h" #include "comtypes.h" #include "main.h" +#include "dbgutil.h" typedef struct _WasmUtilCtx { XW_UtilCtxt super; @@ -44,10 +45,159 @@ wasm_util_getSquareBonus( XW_UtilCtxt* uc, XWEnv xwe, XP_U16 boardSize, return s_buttsBoard[row][col]; } +static const XP_UCHAR* +wasm_getErrString( UtilErrID id, XP_Bool* silent ) +{ + *silent = XP_FALSE; + const char* message = NULL; + + switch( (int)id ) { + case ERR_TILES_NOT_IN_LINE: + message = "All tiles played must be in a line."; + break; + case ERR_NO_EMPTIES_IN_TURN: + message = "Empty squares cannot separate tiles played."; + break; + + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + message = "Too few tiles left to trade."; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + message = "Must play two or more pieces on the first move."; + break; + case ERR_TILES_MUST_CONTACT: + message = "New pieces must contact others already in place (or " + "the middle square on the first move)."; + break; + case ERR_NOT_YOUR_TURN: + message = "You can't do that; it's not your turn!"; + break; + case ERR_NO_PEEK_ROBOT_TILES: + message = "No peeking at the robot's tiles!"; + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case ERR_NO_PEEK_REMOTE_TILES: + message = "No peeking at remote players' tiles!"; + break; + case ERR_REG_UNEXPECTED_USER: + message = "Refused attempt to register unexpected user[s]."; + break; + case ERR_SERVER_DICT_WINS: + message = "Conflict between Host and Guest dictionaries; Host wins."; + XP_WARNF( "GTK may have problems here." ); + break; + case ERR_REG_SERVER_SANS_REMOTE: + message = "At least one player must be marked remote for a game " + "started as Host."; + break; +#endif + + case ERR_NO_EMPTY_TRADE: + message = "No tiles selected; trade cancelled."; + break; + + case ERR_TOO_MANY_TRADE: + message = "More tiles selected than remain in pool."; + break; + + case ERR_NO_HINT_FOUND: + message = "Unable to suggest any moves."; + break; + + case ERR_CANT_UNDO_TILEASSIGN: + message = "Tile assignment can't be undone."; + break; + + case ERR_CANT_HINT_WHILE_DISABLED: + message = "The hint feature is disabled for this game. Enable " + "it for a new game using the Preferences dialog."; + break; + +/* case INFO_REMOTE_CONNECTED: */ +/* message = "Another device has joined the game"; */ +/* break; */ + + case ERR_RELAY_BASE + XWRELAY_ERROR_LOST_OTHER: + *silent = XP_TRUE; + message = "XWRELAY_ERROR_LOST_OTHER"; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_TIMEOUT: + message = "The relay timed you out; other players " + "have left or never showed up."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_YOU: + message = "You were disconnected from relay because it didn't " + "hear from you in too long."; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_OTHER: +/* *silent = XP_TRUE; */ + message = "The relay has lost contact with a device in this game."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_OLDFLAGS: + message = "You need to upgrade your copy of Crosswords."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_SHUTDOWN: + message = "Relay disconnected you to shut down (and probably reboot)."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_BADPROTO: + message = "XWRELAY_ERROR_BADPROTO"; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_RELAYBUSY: + message = "XWRELAY_ERROR_RELAYBUSY"; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_OTHER_DISCON: + *silent = XP_TRUE; /* happens all the time, and shouldn't matter */ + message = "XWRELAY_ERROR_OTHER_DISCON"; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_NO_ROOM: + message = "No such room. Has the host connected yet to reserve it?"; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_DUP_ROOM: + message = "That room is reserved by another host. Rename your room, " + "become a guest, or try again in a few minutes."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_TOO_MANY: + message = "You tried to supply more players than the host expected."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_DELETED: + message = "Game deleted ."; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_NORECONN: + message = "Cannot reconnect."; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_DEADGAME: + message = "Game is listed as dead on relay."; + break; + + default: + XP_LOGF( "no code for error: %d", id ); + message = ""; + } + + return (XP_UCHAR*)message; +} + static void wasm_util_userError( XW_UtilCtxt* uc, XWEnv xwe, UtilErrID id ) { - LOG_FUNC(); + WasmUtilCtx* wuctxt = (WasmUtilCtx*)uc; + Globals* globals = (Globals*)wuctxt->closure; + XP_Bool silent; + const XP_UCHAR* str = wasm_getErrString( id, &silent ); + if ( !silent ) { + main_alert( globals, str ); + } } static void @@ -81,6 +231,7 @@ wasm_util_notifyTrade( XW_UtilCtxt* uc, XWEnv xwe, const XP_UCHAR** tiles, { LOG_FUNC(); } + static void wasm_util_notifyPickTileBlank( XW_UtilCtxt* uc, XWEnv xwe, XP_U16 playerNum, XP_U16 col, XP_U16 row, @@ -92,9 +243,9 @@ wasm_util_notifyPickTileBlank( XW_UtilCtxt* uc, XWEnv xwe, XP_U16 playerNum, static void wasm_util_informNeedPickTiles( XW_UtilCtxt* uc, XWEnv xwe, XP_Bool isInitial, - XP_U16 player, XP_U16 nToPick, - XP_U16 nFaces, const XP_UCHAR** faces, - const XP_U16* counts ) + XP_U16 player, XP_U16 nToPick, + XP_U16 nFaces, const XP_UCHAR** faces, + const XP_U16* counts ) { LOG_FUNC(); } @@ -136,9 +287,17 @@ wasm_util_notifyDupStatus( XW_UtilCtxt* uc, XWEnv xwe, XP_Bool amHost, static void wasm_util_informMove( XW_UtilCtxt* uc, XWEnv xwe, XP_S16 turn, - XWStreamCtxt* expl, XWStreamCtxt* words ) + XWStreamCtxt* expl, XWStreamCtxt* words ) { - LOG_FUNC(); + XWStreamCtxt* useMe = expl; /*!!words ? words : expl;*/ + XP_U16 len = stream_getSize( useMe ); + XP_UCHAR buf[len+1]; + stream_getBytes( useMe, buf, len ); + buf[len] = '\0'; + + WasmUtilCtx* wuctxt = (WasmUtilCtx*)uc; + Globals* globals = (Globals*)wuctxt->closure; + main_alert( globals, buf ); } static void @@ -160,13 +319,16 @@ wasm_util_informNetDict( XW_UtilCtxt* uc, XWEnv xwe, XP_LangCode lang, static void wasm_util_notifyGameOver( XW_UtilCtxt* uc, XWEnv xwe, XP_S16 quitter ) { - LOG_FUNC(); + WasmUtilCtx* wuctxt = (WasmUtilCtx*)uc; + Globals* globals = (Globals*)wuctxt->closure; + main_alert( globals, "Game over" ); } -static XP_Bool wasm_util_engineProgressCallback( XW_UtilCtxt* uc, XWEnv xwe ) +static XP_Bool +wasm_util_engineProgressCallback( XW_UtilCtxt* uc, XWEnv xwe ) { - LOG_FUNC(); - return XP_FALSE; + // LOG_RETURN_VOID(); + return XP_TRUE; } static void @@ -186,10 +348,20 @@ wasm_util_clearTimer( XW_UtilCtxt* uc, XWEnv xwe, XWTimerReason why ) LOG_FUNC(); } +static XP_Bool +on_idle( void* closure ) +{ + WasmUtilCtx* wuctxt = (WasmUtilCtx*)closure; + Globals* globals = (Globals*)wuctxt->closure; + return server_do( globals->game.server, NULL ); +} + static void wasm_util_requestTime( XW_UtilCtxt* uc, XWEnv xwe ) { - LOG_FUNC(); + WasmUtilCtx* wuctxt = (WasmUtilCtx*)uc; + Globals* globals = (Globals*)wuctxt->closure; + main_set_idle( globals, on_idle, wuctxt ); } static XP_Bool @@ -208,20 +380,38 @@ wasm_util_makeEmptyDict( XW_UtilCtxt* uc, XWEnv xwe ) static void wasm_util_notifyIllegalWords( XW_UtilCtxt* uc, XWEnv xwe, BadWordInfo* bwi, - XP_U16 turn, XP_Bool turnLost ) + XP_U16 turn, XP_Bool turnLost ) +{ + XP_UCHAR words[256]; + int offset = 0; + + for ( int ii = 0; ; ) { + offset += XP_SNPRINTF( &words[offset], VSIZE(words) - offset, "%s", + bwi->words[ii] ); + if ( ++ii >= bwi->nWords ) { + break; + } + offset += XP_SNPRINTF( &words[offset], VSIZE(words) - offset, ", " ); + } + + XP_UCHAR buf[256]; + XP_SNPRINTF( buf, VSIZE(buf), "Word[s] \"%s\" not in the current " + "dictionary (%s). Use anyway?", words, bwi->dictName ); + + WasmUtilCtx* wuctxt = (WasmUtilCtx*)uc; + Globals* globals = (Globals*)wuctxt->closure; + main_query( globals, buf, query_proc_notifyMove, uc ); +} + +static void +wasm_util_remSelected( XW_UtilCtxt* uc, XWEnv xwe ) { LOG_FUNC(); } static void -wasm_util_remSelected(XW_UtilCtxt* uc, XWEnv xwe) -{ - LOG_FUNC(); -} - -static void -wasm_util_timerSelected(XW_UtilCtxt* uc, XWEnv xwe, XP_Bool inDuplicateMode, - XP_Bool canPause) +wasm_util_timerSelected( XW_UtilCtxt* uc, XWEnv xwe, XP_Bool inDuplicateMode, + XP_Bool canPause ) { LOG_FUNC(); } @@ -317,7 +507,6 @@ wasm_util_make( MPFORMAL CurGameInfo* gi, XW_DUtilCtxt* dctxt, void* closure ) SET_VTABLE_ENTRY( wuctxt->super.vtable, util_userError, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_makeStreamFromAddr, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_getSquareBonus, wasm ); - SET_VTABLE_ENTRY( wuctxt->super.vtable, util_userError, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_notifyMove, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_notifyTrade, wasm ); @@ -334,6 +523,7 @@ wasm_util_make( MPFORMAL CurGameInfo* gi, XW_DUtilCtxt* dctxt, void* closure ) SET_VTABLE_ENTRY( wuctxt->super.vtable, util_informUndo, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_informNetDict, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_notifyGameOver, wasm ); + SET_VTABLE_ENTRY( wuctxt->super.vtable, util_engineProgressCallback, wasm ); #ifdef XWFEATURE_HILITECELL SET_VTABLE_ENTRY( wuctxt->super.vtable, util_hiliteCell, wasm ); #endif @@ -359,6 +549,9 @@ wasm_util_make( MPFORMAL CurGameInfo* gi, XW_DUtilCtxt* dctxt, void* closure ) SET_VTABLE_ENTRY( wuctxt->super.vtable, util_showChat, wasm ); SET_VTABLE_ENTRY( wuctxt->super.vtable, util_getDevUtilCtxt, wasm ); + size_t sizeInBytes = sizeof(*wuctxt->super.vtable); + assertTableFull( wuctxt->super.vtable, sizeInBytes, "wasmutilctx" ); + LOG_RETURNF( "%p", wuctxt ); return (XW_UtilCtxt*)wuctxt; }