From e64ecfb78d15afd26006afdb109c42e76215de33 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 7 Oct 2012 12:47:06 -0700 Subject: [PATCH] implement resign on top of existing endGame logic, adding "quitter" as new param passed to remote and changing final score formatting and menu items to match. Still need to show old menu on android in case where game has already ended. --- .../XWords4/jni/LocalizedStrIncludes.h | 4 +- .../android/XWords4/res/menu/board_menu.xml | 4 +- .../android/XWords4/res/values/strings.xml | 7 +- .../eehouse/android/xw4/BoardActivity.java | 2 +- .../org/eehouse/android/xw4/jni/UtilCtxt.java | 2 + .../eehouse/android/xw4/jni/UtilCtxtImpl.java | 6 + xwords4/common/server.c | 150 ++++++++++++++---- xwords4/linux/LocalizedStrIncludes.h | 3 + xwords4/linux/gtkmain.c | 6 +- xwords4/linux/linuxutl.c | 5 +- xwords4/linux/main.h | 1 + 11 files changed, 153 insertions(+), 37 deletions(-) diff --git a/xwords4/android/XWords4/jni/LocalizedStrIncludes.h b/xwords4/android/XWords4/jni/LocalizedStrIncludes.h index a4f86f4b5..b9afeee58 100644 --- a/xwords4/android/XWords4/jni/LocalizedStrIncludes.h +++ b/xwords4/android/XWords4/jni/LocalizedStrIncludes.h @@ -28,6 +28,8 @@ # define STRD_TURN_SCORE 22 # define STRD_REMAINS_HEADER 23 # define STRD_REMAINS_EXPL 24 +# define STR_RESIGNED 25 +# define STR_WINNER 26 -# define N_AND_USER_STRINGS 24 +# define N_AND_USER_STRINGS 26 #endif diff --git a/xwords4/android/XWords4/res/menu/board_menu.xml b/xwords4/android/XWords4/res/menu/board_menu.xml index eb888921e..0b75e22b0 100644 --- a/xwords4/android/XWords4/res/menu/board_menu.xml +++ b/xwords4/android/XWords4/res/menu/board_menu.xml @@ -29,8 +29,8 @@ android:title="@string/board_menu_game_left" /> - + diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 61769251d..c2f0f13fc 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -693,6 +693,7 @@ is not yet over, gives you a choice whether to end it now, and if you decline does nothing. --> Final scores + Resign - Are you sure you want to end the game now? + Are you sure you want to resign? @@ -2110,5 +2111,9 @@ New version of %s Tap to download + + Resigned + + Winner 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 36671ff0e..47c462c96 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -731,7 +731,7 @@ public class BoardActivity extends XWActivity R.string.history_title ); break; - case R.id.board_menu_game_final: + case R.id.board_menu_game_resign: m_jniThread.handle( JNIThread.JNICmd.CMD_FINAL, R.string.history_title ); break; 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 8e975a334..06a518e4f 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 @@ -84,6 +84,8 @@ public interface UtilCtxt { static final int STRD_TURN_SCORE = 22; static final int STRD_REMAINS_HEADER = 23; static final int STRD_REMAINS_EXPL = 24; + static final int STR_RESIGNED = 25; + static final int STR_WINNER = 26; String getUserString( int stringCode ); 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 ddbed884c..8fb99b467 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 @@ -181,6 +181,12 @@ public class UtilCtxtImpl implements UtilCtxt { case UtilCtxt.STRD_REMAINS_EXPL: id = R.string.strd_remains_expl; break; + case UtilCtxt.STR_RESIGNED: + id = R.string.str_resigned; + break; + case UtilCtxt.STR_WINNER: + id = R.string.str_winner; + break; default: DbgUtils.logf( "no such stringCode: %d", stringCode ); diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 0ddb63a11..7bf529ee0 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -85,6 +85,7 @@ typedef struct ServerNonvolatiles { XW_State gameState; XW_State stateAfterShow; XP_S8 currentTurn; /* invalid when game is over */ + XP_S8 quitter; /* -1 unless somebody resigned */ XP_U8 pendingRegistrations; XP_Bool showRobotScores; XP_Bool sortNewTiles; @@ -134,8 +135,8 @@ static void assignTilesToAll( ServerCtxt* server ); static void resetEngines( ServerCtxt* server ); static void nextTurn( ServerCtxt* server, XP_S16 nxtTurn ); -static void doEndGame( ServerCtxt* server ); -static void endGameInternal( ServerCtxt* server, GameEndReason why ); +static void doEndGame( ServerCtxt* server, XP_S16 quitter ); +static void endGameInternal( ServerCtxt* server, GameEndReason why, XP_S16 quitter ); static void badWordMoveUndoAndTellUser( ServerCtxt* server, BadWordInfo* bwi ); static XP_Bool tileCountsOk( const ServerCtxt* server ); @@ -249,6 +250,7 @@ initServer( ServerCtxt* server ) #ifdef STREAM_VERS_BIGBOARD server->nv.streamVersion = STREAM_SAVE_PREVWORDS; /* default to old */ #endif + server->nv.quitter = -1; } /* initServer */ ServerCtxt* @@ -298,6 +300,9 @@ getNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers ) } nv->currentTurn = (XP_S8)stream_getBits( stream, NPLAYERS_NBITS ) - 1; + if ( STREAM_VERS_DICTNAME <= version ) { + nv->quitter = (XP_S8)stream_getBits( stream, NPLAYERS_NBITS ) - 1; + } nv->pendingRegistrations = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); for ( ii = 0; ii < nPlayers; ++ii ) { @@ -332,6 +337,7 @@ putNV( XWStreamCtxt* stream, const ServerNonvolatiles* nv, XP_U16 nPlayers ) /* +1: make -1 (NOTURN) into a positive number */ stream_putBits( stream, NPLAYERS_NBITS, nv->currentTurn+1 ); + stream_putBits( stream, NPLAYERS_NBITS, nv->quitter+1 ); stream_putBits( stream, NPLAYERS_NBITS, nv->pendingRegistrations ); for ( ii = 0; ii < nPlayers; ++ii ) { @@ -1054,7 +1060,7 @@ server_do( ServerCtxt* server ) #endif /* XWFEATURE_STANDALONE_ONLY */ case XWSTATE_NEEDSEND_ENDGAME: - endGameInternal( server, END_REASON_OUT_OF_TILES ); + endGameInternal( server, END_REASON_OUT_OF_TILES, -1 ); break; case XWSTATE_NEED_SHOWSCORE: @@ -2399,14 +2405,30 @@ server_getLastMoveTime( const ServerCtxt* server ) } static void -doEndGame( ServerCtxt* server ) +doEndGame( ServerCtxt* server, XP_S16 quitter ) { SETSTATE( server, XWSTATE_GAMEOVER ); setTurn( server, -1 ); + server->nv.quitter = quitter; (*server->vol.gameOverListener)( server->vol.gameOverData ); } /* doEndGame */ +static void +putQuitter( const ServerCtxt* server, XWStreamCtxt* stream, XP_S16 quitter ) +{ + if ( STREAM_VERS_DICTNAME <= server->nv.streamVersion ) { + stream_putU8( stream, quitter ); + } +} + +static void +getQuitter( const ServerCtxt* server, XWStreamCtxt* stream, XP_S16* quitter ) +{ + *quitter = STREAM_VERS_DICTNAME <= server->nv.streamVersion + ? stream_getU8( stream ) : -1; +} + /* Somebody wants to end the game. * * If I'm the server, I send a END_GAME message to all clients. If I'm a @@ -2415,7 +2437,7 @@ doEndGame( ServerCtxt* server ) * GAME_OVER message to all clients including the one that requested it. */ static void -endGameInternal( ServerCtxt* server, GameEndReason XP_UNUSED(why) ) +endGameInternal( ServerCtxt* server, GameEndReason XP_UNUSED(why), XP_S16 quitter ) { XP_ASSERT( server->nv.gameState != XWSTATE_GAMEOVER ); @@ -2427,10 +2449,11 @@ endGameInternal( ServerCtxt* server, GameEndReason XP_UNUSED(why) ) XWStreamCtxt* stream; stream = messageStreamWithHeader( server, devIndex, XWPROTO_END_GAME ); + putQuitter( server, stream, quitter ); stream_destroy( stream ); } #endif - doEndGame( server ); + doEndGame( server, quitter ); #ifndef XWFEATURE_STANDALONE_ONLY } else { @@ -2449,7 +2472,7 @@ server_endGame( ServerCtxt* server ) { XW_State gameState = server->nv.gameState; if ( gameState < XWSTATE_GAMEOVER && gameState >= XWSTATE_INTURN ) { - endGameInternal( server, END_REASON_USER_REQUEST ); + endGameInternal( server, END_REASON_USER_REQUEST, server->nv.currentTurn ); } } /* server_endGame */ @@ -2711,7 +2734,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) XP_FREE( server->mpool, msg ); #endif } else if ( readStreamHeader( server, incoming ) ) { - + XP_S16 quitter; switch( code ) { /* case XWPROTO_MOVEMADE_INFO: */ /* accepted = client_reflectMoveMade( server, incoming ); */ @@ -2763,11 +2786,13 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) break; case XWPROTO_CLIENT_REQ_END_GAME: - endGameInternal( server, END_REASON_USER_REQUEST ); + getQuitter( server, incoming, &quitter ); + endGameInternal( server, END_REASON_USER_REQUEST, quitter ); accepted = XP_TRUE; break; case XWPROTO_END_GAME: - doEndGame( server ); + getQuitter( server, incoming, &quitter ); + doEndGame( server, quitter ); accepted = XP_TRUE; break; default: @@ -2927,21 +2952,63 @@ server_figureFinishBonus( const ServerCtxt* server, XP_U16 turn ) #endif #define IMPOSSIBLY_LOW_SCORE -1000 +#if 0 +static void +printPlayer( const ServerCtxt* server, XWStreamCtxt* stream, XP_U16 index, + const XP_UCHAR* placeBuf, ScoresArray* scores, + ScoresArray* tilePenalties, XP_U16 place ) +{ + XP_UCHAR buf[128]; + CurGameInfo* gi = server->vol.gi; + ModelCtxt* model = server->vol.model; + XP_Bool firstDone = model_getNumTilesTotal( model, index ) == 0; + XP_UCHAR tmpbuf[48]; + XP_U16 addSubKey = firstDone? STRD_REMAINING_TILES_ADD : STRD_UNUSED_TILES_SUB; + const XP_UCHAR* addSubString = util_getUserString( server->vol.util, addSubKey ); + XP_UCHAR* timeStr = (XP_UCHAR*)""; + XP_UCHAR timeBuf[16]; + if ( gi->timerEnabled ) { + XP_U16 penalty = player_timePenalty( gi, index ); + if ( penalty > 0 ) { + XP_SNPRINTF( timeBuf, sizeof(timeBuf), + util_getUserString( server->vol.util, + STRD_TIME_PENALTY_SUB ), + penalty ); /* positive for formatting */ + timeStr = timeBuf; + } + } + + XP_SNPRINTF( tmpbuf, sizeof(tmpbuf), addSubString, + firstDone? + tilePenalties->arr[index]: + -tilePenalties->arr[index] ); + + XP_SNPRINTF( buf, sizeof(buf), + (XP_UCHAR*)"[%s] %s: %d" XP_CR " (%d %s%s)", + placeBuf, emptyStringIfNull(gi->players[index].name), + scores->arr[index], model_getPlayerScore( model, index ), + tmpbuf, timeStr ); + if ( 0 < place ) { + stream_catString( stream, XP_CR ); + } + stream_catString( stream, buf ); +} /* printPlayer */ +#endif + void server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) { ScoresArray scores; ScoresArray tilePenalties; - XP_S16 highestIndex; - XP_S16 highestScore; - XP_U16 place, nPlayers, ii; - XP_S16 curScore; + XP_U16 place, nPlayers; + XP_S16 quitter = server->nv.quitter; + XP_Bool quitterDone = XP_FALSE; + XP_USE(quitter); ModelCtxt* model = server->vol.model; const XP_UCHAR* addString = util_getUserString( server->vol.util, STRD_REMAINING_TILES_ADD ); const XP_UCHAR* subString = util_getUserString( server->vol.util, STRD_UNUSED_TILES_SUB ); - XP_UCHAR timeBuf[16]; XP_UCHAR* timeStr; CurGameInfo* gi = server->vol.gi; @@ -2951,29 +3018,46 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) nPlayers = gi->nPlayers; - for ( place = 1; ; ++place ) { + for ( place = 1; !quitterDone; ++place ) { + XP_UCHAR timeBuf[16]; + XP_UCHAR buf[128]; + XP_S16 highestScore = IMPOSSIBLY_LOW_SCORE; + XP_S16 highestIndex = -1; + const XP_UCHAR* placeStr = NULL; + XP_UCHAR placeBuf[32]; XP_UCHAR tmpbuf[48]; - XP_UCHAR buf[128]; + XP_U16 ii, placeKey = 0; XP_Bool firstDone; - highestScore = IMPOSSIBLY_LOW_SCORE; - highestIndex = -1; - + /* Find the next player we should print */ for ( ii = 0; ii < nPlayers; ++ii ) { - if ( scores.arr[ii] > highestScore ) { + if ( quitter != ii && scores.arr[ii] > highestScore ) { highestIndex = ii; highestScore = scores.arr[ii]; } } if ( highestIndex == -1 ) { - break; /* we're done */ - } else if ( place > 1 ) { - stream_catString( stream, XP_CR ); + if ( quitter >= 0 ) { + XP_ASSERT( !quitterDone ); + highestIndex = quitter; + quitterDone = XP_TRUE; + placeKey = STR_RESIGNED; + } else { + break; /* we're done */ + } + } else if ( place == 1 ) { + placeKey = STR_WINNER; } - scores.arr[highestIndex] = IMPOSSIBLY_LOW_SCORE; - curScore = model_getPlayerScore( model, highestIndex ); + if ( !placeStr ) { + if ( 0 < placeKey ) { + placeStr = util_getUserString( server->vol.util, placeKey ); + } else { + XP_SNPRINTF( placeBuf, VSIZE(placeBuf), "#%d", place ); + placeStr = placeBuf; + } + } timeStr = (XP_UCHAR*)""; if ( gi->timerEnabled ) { @@ -2996,11 +3080,19 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) -tilePenalties.arr[highestIndex] ); XP_SNPRINTF( buf, sizeof(buf), - (XP_UCHAR*)"[%d] %s: %d" XP_CR " (%d %s%s)", - place, + (XP_UCHAR*)"[%s] %s: %d" XP_CR " (%d %s%s)", placeStr, emptyStringIfNull(gi->players[highestIndex].name), - highestScore, curScore, tmpbuf, timeStr ); + scores.arr[highestIndex], + model_getPlayerScore( model, highestIndex ), + tmpbuf, timeStr ); + + if ( 1 < place ) { + stream_catString( stream, XP_CR ); + } stream_catString( stream, buf ); + + /* Don't consider this one next time around */ + scores.arr[highestIndex] = IMPOSSIBLY_LOW_SCORE; } } /* server_writeFinalScores */ diff --git a/xwords4/linux/LocalizedStrIncludes.h b/xwords4/linux/LocalizedStrIncludes.h index 66637efbe..1f75b4c02 100644 --- a/xwords4/linux/LocalizedStrIncludes.h +++ b/xwords4/linux/LocalizedStrIncludes.h @@ -58,6 +58,9 @@ enum { STRD_REMAINS_HEADER, STRD_REMAINS_EXPL, + STR_RESIGNED, + STR_WINNER, + STR_LAST }; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 097bb9c88..b5a0a5de9 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -765,8 +765,9 @@ final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) catFinalScores( &globals->cGlobals ); } else { if ( gtkask( globals->window, - "Are you sure everybody wants to end the game now?", + "Are you sure you want to resign?", GTK_BUTTONS_YES_NO ) ) { + globals->cGlobals.manualFinal = XP_TRUE; server_endGame( globals->cGlobals.game.server ); gameOver = TRUE; } @@ -1412,7 +1413,8 @@ gtkShowFinalScores( const GtkAppGlobals* globals ) text = strFromStream( stream ); stream_destroy( stream ); - (void)gtkask_timeout( globals->window, text, GTK_BUTTONS_OK, 500 ); + XP_U16 timeout = cGlobals->manualFinal? 0 : 500; + (void)gtkask_timeout( globals->window, text, GTK_BUTTONS_OK, timeout ); free( text ); } /* gtkShowFinalScores */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 9650b6bb3..94c1cdcf1 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -335,7 +335,10 @@ linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code ) return (XP_UCHAR*)"%d tiles left in pool."; case STRD_REMAINS_EXPL: return (XP_UCHAR*)"%d tiles left in pool and all tray[s]:\n"; - + case STR_RESIGNED: + return "Resigned"; + case STR_WINNER: + return "Winner"; default: return (XP_UCHAR*)"unknown code to linux_util_getUserString"; } diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index e3118d603..fd9b3bf70 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -165,6 +165,7 @@ struct CommonGlobals { XWGame game; XP_U16 lastNTilesToUse; XP_U16 lastStreamSize; + XP_Bool manualFinal; /* use asked for final scores */ SocketChangedFunc socketChanged; void* socketChangedClosure;