From 694953c820e47de1fde6c536961edbb7ed970662 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 16 Jul 2021 11:50:05 -0700 Subject: [PATCH] add support for trays with up to 9 tiles --- .../eehouse/android/xw4/BoardDelegate.java | 2 +- .../eehouse/android/xw4/EnableSMSAlert.java | 4 +- .../android/xw4/GameConfigDelegate.java | 42 +++- .../eehouse/android/xw4/InviteDelegate.java | 5 +- .../android/xw4/StudyListDelegate.java | 2 + .../java/org/eehouse/android/xw4/Utils.java | 7 + .../java/org/eehouse/android/xw4/XWApp.java | 2 +- .../java/org/eehouse/android/xw4/XWPrefs.java | 5 + .../eehouse/android/xw4/jni/BoardDims.java | 2 +- .../eehouse/android/xw4/jni/CurGameInfo.java | 18 ++ .../eehouse/android/xw4/jni/DUtilCtxt.java | 4 + .../eehouse/android/xw4/loc/LocDelegate.java | 6 +- .../app/src/main/res/layout/game_config.xml | 23 +- .../app/src/main/res/values/common_rsrc.xml | 8 + .../app/src/main/res/values/strings.xml | 10 + .../app/src/main/res/xml/prefs_dflts.xml | 7 + xwords4/android/jni/LocalizedStrIncludes.h | 3 +- xwords4/android/jni/xwjni.c | 36 +-- xwords4/common/board.c | 18 +- xwords4/common/board.h | 3 +- xwords4/common/comtypes.h | 14 +- xwords4/common/engine.c | 9 +- xwords4/common/game.c | 16 ++ xwords4/common/gameinfo.h | 2 + xwords4/common/model.c | 57 ++--- xwords4/common/model.h | 10 +- xwords4/common/modelp.h | 2 +- xwords4/common/movestak.c | 37 ++- xwords4/common/movestak.h | 6 +- xwords4/common/mscore.c | 23 +- xwords4/common/server.c | 22 +- xwords4/common/strutils.c | 21 +- xwords4/common/strutils.h | 2 + xwords4/common/tray.c | 34 +-- xwords4/linux/LocalizedStrIncludes.h | 2 + xwords4/linux/gtknewgame.c | 212 ++++++++++++------ xwords4/linux/lindutil.c | 2 + xwords4/linux/linuxmain.c | 9 + xwords4/linux/scripts/discon_ok2.py | 1 + 39 files changed, 483 insertions(+), 205 deletions(-) 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 ae186efd0..e82be865a 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 @@ -847,7 +847,7 @@ public class BoardDelegate extends DelegateBase case R.id.board_menu_done: int nTiles = XwJNI.model_getNumTilesInTray( m_jniGamePtr, m_view.getCurPlayer() ); - if ( XWApp.MAX_TRAY_TILES > nTiles ) { + if ( m_gi.traySize > nTiles ) { makeNotAgainBuilder( R.string.not_again_done, R.string.key_notagain_done, Action.COMMIT_ACTION ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/EnableSMSAlert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/EnableSMSAlert.java index bdaf8a5bd..1ff802460 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/EnableSMSAlert.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/EnableSMSAlert.java @@ -51,14 +51,12 @@ public class EnableSMSAlert extends DlgDelegateAlert { View layout = LocUtils.inflate( context, R.layout.confirm_sms ); mSpinner = (Spinner)layout.findViewById( R.id.confirm_sms_reasons ); - OnItemSelectedListener onItemSel = new OnItemSelectedListener() { + OnItemSelectedListener onItemSel = new Utils.OnNothingSelDoesNothing() { @Override public void onItemSelected( AdapterView parent, View view, int position, long id ) { checkEnableButton( (AlertDialog)getDialog() ); } - @Override - public void onNothingSelected( AdapterView parent ) {} }; mSpinner.setOnItemSelectedListener( onItemSel ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java index b7a7022af..6d413c7c2 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameConfigDelegate.java @@ -100,6 +100,7 @@ public class GameConfigDelegate extends DelegateBase // private Spinner m_connectSpinner; private Spinner m_phoniesSpinner; private Spinner m_boardsizeSpinner; + private Spinner m_traysizeSpinner; private Spinner m_langSpinner; private Spinner m_smartnessSpinner; private TextView m_connLabel; @@ -126,6 +127,7 @@ public class GameConfigDelegate extends DelegateBase R.id.duplicate_check, R.id.pick_faceup, R.id.boardsize_spinner, + R.id.traysize_spinner, R.id.use_timer, R.id.timer_minutes_edit, R.id.smart_robot, @@ -524,6 +526,8 @@ public class GameConfigDelegate extends DelegateBase .getSpinner(); m_boardsizeSpinner = ((LabeledSpinner)findViewById( R.id.boardsize_spinner )) .getSpinner(); + m_traysizeSpinner = ((LabeledSpinner)findViewById( R.id.traysize_spinner )) + .getSpinner(); m_smartnessSpinner = ((LabeledSpinner)findViewById( R.id.smart_robot )) .getSpinner(); @@ -680,6 +684,31 @@ public class GameConfigDelegate extends DelegateBase setChecked( R.id.pick_faceup, m_gi.allowPickTiles ); setBoardsizeSpinner(); + + final int[] curSel = {-1}; + String val = String.format( "%d", m_gi.traySize ); + SpinnerAdapter adapter = m_traysizeSpinner.getAdapter(); + for ( int ii = 0; ii < adapter.getCount(); ++ii ) { + if ( val.equals( adapter.getItem(ii) ) ) { + m_traysizeSpinner.setSelection( ii ); + curSel[0] = ii; + break; + } + } + m_traysizeSpinner + .setOnItemSelectedListener( new Utils.OnNothingSelDoesNothing() { + @Override + public void onItemSelected( AdapterView parent, View spinner, + int position, long id ) { + if ( curSel[0] != position ) { + curSel[0] = position; + makeNotAgainBuilder( R.string.not_again_traysize, + R.string.key_na_traysize ) + .show(); + } + } + } ); + } } // loadGame @@ -1014,8 +1043,7 @@ public class GameConfigDelegate extends DelegateBase dictsSpinner.setPrompt( getString( R.string.dicts_list_prompt_fmt, langName ) ); - OnItemSelectedListener onSel = - new OnItemSelectedListener() { + OnItemSelectedListener onSel = new Utils.OnNothingSelDoesNothing() { @Override public void onItemSelected( AdapterView parentView, View selectedItemView, @@ -1029,9 +1057,6 @@ public class GameConfigDelegate extends DelegateBase m_gi.dictLang ); } } - - @Override - public void onNothingSelected(AdapterView parentView) {} }; ArrayAdapter adapter = @@ -1048,8 +1073,7 @@ public class GameConfigDelegate extends DelegateBase final LangsArrayAdapter adapter = DictLangCache.getLangsAdapter( m_activity ); - OnItemSelectedListener onSel = - new OnItemSelectedListener() { + OnItemSelectedListener onSel = new Utils.OnNothingSelDoesNothing() { @Override public void onItemSelected(AdapterView parentView, View selectedItemView, @@ -1067,9 +1091,6 @@ public class GameConfigDelegate extends DelegateBase } } } - - @Override - public void onNothingSelected(AdapterView parentView) {} }; String lang = DictLangCache.getLangName( m_activity, m_gi.dictLang ); @@ -1306,6 +1327,7 @@ public class GameConfigDelegate extends DelegateBase position = m_boardsizeSpinner.getSelectedItemPosition(); m_gi.boardSize = positionToSize( position ); + m_gi.traySize = Integer.parseInt( m_traysizeSpinner.getSelectedItem().toString() ); if ( m_conTypes.contains( CommsConnType.COMMS_CONN_RELAY ) ) { m_car.ip_relay_seeksPublicRoom = m_joinPublicCheck.isChecked(); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteDelegate.java index 2ad5d8ffe..cb257dd0f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteDelegate.java @@ -360,7 +360,8 @@ abstract class InviteDelegate extends DelegateBase } spinner.setAdapter( adapter ); spinner.setVisibility( View.VISIBLE ); - spinner.setOnItemSelectedListener( new OnItemSelectedListener() { + spinner.setOnItemSelectedListener( new Utils.OnNothingSelDoesNothing() { + @Override public void onItemSelected( AdapterView parent, View view, int pos, long id ) @@ -368,8 +369,6 @@ abstract class InviteDelegate extends DelegateBase m_counts.put( item, 1 + pos ); tryEnable(); } - - public void onNothingSelected( AdapterView parent ) {} } ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java index 12edcbba7..b0eac1930 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/StudyListDelegate.java @@ -226,6 +226,7 @@ public class StudyListDelegate extends ListDelegateBase ////////////////////////////////////////////////// // AdapterView.OnItemSelectedListener interface ////////////////////////////////////////////////// + @Override public void onItemSelected( AdapterView parent, View view, int position, long id ) { @@ -234,6 +235,7 @@ public class StudyListDelegate extends ListDelegateBase loadList(); // because language has changed } + @Override public void onNothingSelected( AdapterView parent ) { } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java index 221c9e8e2..2e2a98b55 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java @@ -49,6 +49,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; @@ -820,4 +821,10 @@ public class Utils { } } } + + static abstract class OnNothingSelDoesNothing + implements AdapterView.OnItemSelectedListener { + @Override + public void onNothingSelected(AdapterView parentView) {} + } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java index 8b4acff93..d3e99ab1f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java @@ -49,7 +49,7 @@ public class XWApp extends Application public static final boolean OFFER_DUALPANE = false; public static final String SMS_PUBLIC_HEADER = "-XW4"; - public static final int MAX_TRAY_TILES = 7; // comtypes.h + public static final int MIN_TRAY_TILES = 7; // comtypes.h public static final int SEL_COLOR = Color.argb( 0xFF, 0x09, 0x70, 0x93 ); public static final int GREEN = 0xFF00AF00; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java index 1bc06e9b8..71087fe65 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java @@ -543,6 +543,11 @@ public class XWPrefs { return result; } + public static int getDefaultTraySize( Context context ) + { + return getPrefsInt( context, R.string.key_tray_size, XWApp.MIN_TRAY_TILES ); + } + public static void setAddrTypes( Context context, CommsConnTypeSet set ) { int flags = set.toInt(); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/BoardDims.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/BoardDims.java index 88d461396..afcf30fec 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/BoardDims.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/BoardDims.java @@ -27,7 +27,7 @@ public class BoardDims { public int width, height; // of the bitmap public int scoreLeft, scoreWidth, scoreHt; public int boardWidth, boardHt; - public int trayLeft, trayTop, trayWidth, trayHt; + public int trayLeft, trayTop, trayWidth, trayHt, traySize; public int cellSize, maxCellSize; public int timerWidth; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CurGameInfo.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CurGameInfo.java index 3fe47024c..22889c30a 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CurGameInfo.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/CurGameInfo.java @@ -37,6 +37,8 @@ import org.eehouse.android.xw4.DictUtils; import org.eehouse.android.xw4.Log; import org.eehouse.android.xw4.R; import org.eehouse.android.xw4.Utils; +import org.eehouse.android.xw4.XWApp; +import org.eehouse.android.xw4.XWPrefs; import org.eehouse.android.xw4.loc.LocUtils; public class CurGameInfo implements Serializable { @@ -45,6 +47,8 @@ public class CurGameInfo implements Serializable { public static final int MAX_NUM_PLAYERS = 4; private static final String BOARD_SIZE = "BOARD_SIZE"; + private static final String TRAY_SIZE = "TRAY_SIZE"; + private static final String BINGO_MIN = "BINGO_MIN"; private static final String NO_HINTS = "NO_HINTS"; private static final String TIMER = "TIMER"; private static final String ALLOW_PICK = "ALLOW_PICK"; @@ -61,6 +65,8 @@ public class CurGameInfo implements Serializable { public int gameSeconds; public int nPlayers; public int boardSize; + public int traySize; + public int bingoMin; public int forceChannel; public DeviceRole serverRole; @@ -89,6 +95,8 @@ public class CurGameInfo implements Serializable { gameSeconds = inDuplicateMode ? (5 * 60) : 60 * nPlayers * CommonPrefs.getDefaultPlayerMinutes( context ); boardSize = CommonPrefs.getDefaultBoardSize( context ); + traySize = XWPrefs.getDefaultTraySize( context ); + bingoMin = XWApp.MIN_TRAY_TILES; players = new LocalPlayer[MAX_NUM_PLAYERS]; serverRole = isNetworked ? DeviceRole.SERVER_ISCLIENT : DeviceRole.SERVER_STANDALONE; @@ -140,6 +148,8 @@ public class CurGameInfo implements Serializable { nPlayers = src.nPlayers; gameSeconds = src.gameSeconds; boardSize = src.boardSize; + traySize = src.traySize; + bingoMin = src.bingoMin; players = new LocalPlayer[MAX_NUM_PLAYERS]; serverRole = src.serverRole; dictName = src.dictName; @@ -190,6 +200,8 @@ public class CurGameInfo implements Serializable { try { JSONObject obj = new JSONObject() .put( BOARD_SIZE, boardSize ) + .put( TRAY_SIZE, traySize ) + .put( BINGO_MIN, bingoMin ) .put( NO_HINTS, hintsNotAllowed ) .put( DUP, inDuplicateMode ) .put( TIMER, timerEnabled ) @@ -210,6 +222,8 @@ public class CurGameInfo implements Serializable { try { JSONObject obj = new JSONObject( jsonData ); boardSize = obj.optInt( BOARD_SIZE, boardSize ); + traySize = obj.optInt( TRAY_SIZE, traySize ); + bingoMin = obj.optInt( BINGO_MIN, bingoMin ); hintsNotAllowed = obj.optBoolean( NO_HINTS, hintsNotAllowed ); inDuplicateMode = obj.optBoolean( DUP, inDuplicateMode ); timerEnabled = obj.optBoolean( TIMER, timerEnabled ); @@ -287,6 +301,8 @@ public class CurGameInfo implements Serializable { || serverRole != other.serverRole || dictLang != other.dictLang || boardSize != other.boardSize + || traySize != other.traySize + || bingoMin != other.bingoMin || hintsNotAllowed != other.hintsNotAllowed || inDuplicateMode != other.inDuplicateMode || allowPickTiles != other.allowPickTiles @@ -320,6 +336,8 @@ public class CurGameInfo implements Serializable { && gameSeconds == other.gameSeconds && nPlayers == other.nPlayers && boardSize == other.boardSize + && traySize == other.traySize + && bingoMin == other.bingoMin && forceChannel == other.forceChannel && hintsNotAllowed == other.hintsNotAllowed && inDuplicateMode == other.inDuplicateMode diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java index 912b23daa..8c7261ff7 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java @@ -128,6 +128,7 @@ public class DUtilCtxt { private static final int STRD_DUP_TRADED = 28; private static final int STRSD_DUP_ONESCORE = 29; private static final int STR_PENDING_PLAYER = 30; + private static final int STR_BONUS_ALL_SUB = 31; public String getUserString( final int stringCode ) { @@ -184,6 +185,9 @@ public class DUtilCtxt { case STR_BONUS_ALL: id = R.string.str_bonus_all; break; + case STR_BONUS_ALL_SUB: + id = R.string.str_bonus_all_fmt; + break; case STRD_TURN_SCORE: id = R.string.strd_turn_score_fmt; break; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/loc/LocDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/loc/LocDelegate.java index ae4cff023..bfbfac2bd 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/loc/LocDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/loc/LocDelegate.java @@ -117,6 +117,7 @@ public class LocDelegate extends ListDelegateBase ////////////////////////////////////////////////// // AdapterView.OnItemSelectedListener interface ////////////////////////////////////////////////// + @Override public void onItemSelected( AdapterView parent, View view, int position, long id ) { @@ -124,7 +125,6 @@ public class LocDelegate extends ListDelegateBase makeNewAdapter(); } - public void onNothingSelected( AdapterView parent ) - { - } + @Override + public void onNothingSelected( AdapterView parent ) {} } diff --git a/xwords4/android/app/src/main/res/layout/game_config.xml b/xwords4/android/app/src/main/res/layout/game_config.xml index 6a416ca40..e766a6456 100644 --- a/xwords4/android/app/src/main/res/layout/game_config.xml +++ b/xwords4/android/app/src/main/res/layout/game_config.xml @@ -263,11 +263,18 @@ /> - + + + + + + key_init_nethintsallowed key_init_autojuggle key_board_size + key_tray_size key_initial_player_minutes key_default_language key_default_dict @@ -157,6 +158,7 @@ key_na_perms_phonestate key_na_perms_storage_dicts key_na_newFeatureFilter + key_na_traysize key_na_dupstatus_host key_na_dupstatus_guest @@ -186,6 +188,12 @@ 11x11 + + 7 + 8 + 9 + + @string/phonies_ignore @string/phonies_warn diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 18246c15f..97cc32ea0 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -702,6 +702,8 @@ \u0020vs.\u0020 Bonus for using all tiles: 50\n + + Bonus for using %1$d tiles: 50\n Score for turn: %1$d\n @@ -811,6 +813,7 @@ Timer minutes per player Board size + Tiles in rack + This new setting changes the + number of tiles in the rack.\n\nNote: if anyone in your networked + game is using an older version of CrossWords this setting will + revert to 7 for everyone in the game. + Email author… Comment about CrossWords diff --git a/xwords4/android/app/src/main/res/xml/prefs_dflts.xml b/xwords4/android/app/src/main/res/xml/prefs_dflts.xml index 69f99f8bd..411968f82 100644 --- a/xwords4/android/app/src/main/res/xml/prefs_dflts.xml +++ b/xwords4/android/app/src/main/res/xml/prefs_dflts.xml @@ -65,6 +65,13 @@ android:numeric="decimal" /> + visible = (XP_Bool)stream_getBits( stream, 1 ); if ( STREAM_VERS_MODELDIVIDER > version ) { - (void)stream_getBits( stream, NTILES_NBITS ); + (void)stream_getBits( stream, NTILES_NBITS_7 ); } - pti->traySelBits = (TileBit)stream_getBits( stream, - MAX_TRAY_TILES ); + XP_U16 nBits = STREAM_VERS_NINETILES <= version ? MAX_TRAY_TILES : 7; + pti->traySelBits = (TileBit)stream_getBits( stream, nBits ); pti->tradeInProgress = (XP_Bool)stream_getBits( stream, 1 ); if ( version >= STREAM_VERS_KEYNAV ) { @@ -287,7 +287,6 @@ board_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, ModelCtxt* model pti->limits.bottom = stream_getBits( stream, 4 ); } #endif - } board->selPlayer = (XP_U8)stream_getBits( stream, PLAYERNUM_NBITS ); @@ -351,6 +350,7 @@ board_writeToStream( const BoardCtxt* board, XWStreamCtxt* stream ) stream_putBits( stream, 1, arrow->vert ); stream_putBits( stream, 1, arrow->visible ); + XP_ASSERT( CUR_STREAM_VERS == stream_getVersion(stream) ); stream_putBits( stream, MAX_TRAY_TILES, pti->traySelBits ); stream_putBits( stream, 1, pti->tradeInProgress ); @@ -464,8 +464,7 @@ board_figureLayout( BoardCtxt* board, XWEnv xwe, const CurGameInfo* gi, XP_U16 scoreWidth, XP_U16 fontWidth, XP_U16 fontHt, XP_Bool squareTiles, BoardDims* dimsp ) { - BoardDims ldims; - XP_MEMSET( &ldims, 0, sizeof(ldims) ); + BoardDims ldims = {0}; XP_U16 nCells = gi->boardSize; XP_U16 maxCellSize = 8 * fontHt; @@ -569,6 +568,7 @@ board_figureLayout( BoardCtxt* board, XWEnv xwe, const CurGameInfo* gi, ldims.boardHt = cellSize * nCells; ldims.trayTop = ldims.top + scoreHt + (cellSize * (nCells-nToScroll)); + ldims.traySize = gi->traySize; ldims.height = #ifdef FORCE_SQUARE ldims.width @@ -629,7 +629,7 @@ board_applyLayout( BoardCtxt* board, XWEnv xwe, const BoardDims* dims ) dims->top, dims->timerWidth, dims->scoreHt ); board_setTrayLoc( board, xwe, dims->trayLeft, dims->trayTop, - dims->trayWidth, dims->trayHt ); + dims->trayWidth, dims->trayHt, dims->traySize ); } #endif @@ -1692,7 +1692,7 @@ onBorderCanScroll( const BoardCtxt* board, SDIndex indx, void board_setTrayLoc( BoardCtxt* board, XWEnv xwe, XP_U16 trayLeft, XP_U16 trayTop, - XP_U16 trayWidth, XP_U16 trayHeight ) + XP_U16 trayWidth, XP_U16 trayHeight, XP_U16 nTiles ) { /* XP_LOGF( "%s(%d,%d,%d,%d)", __func__, trayLeft, trayTop, */ /* trayWidth, trayHeight ); */ @@ -1709,7 +1709,7 @@ board_setTrayLoc( BoardCtxt* board, XWEnv xwe, XP_U16 trayLeft, XP_U16 trayTop, dividerWidth = dividerWidth + ((trayWidth - dividerWidth) % MAX_TRAY_TILES); - board->trayScaleH = (trayWidth - dividerWidth) / MAX_TRAY_TILES; + board->trayScaleH = (trayWidth - dividerWidth) / nTiles; board->trayScaleV = trayHeight; board->dividerWidth = dividerWidth; diff --git a/xwords4/common/board.h b/xwords4/common/board.h index 26415fd83..439332b9e 100644 --- a/xwords4/common/board.h +++ b/xwords4/common/board.h @@ -91,6 +91,7 @@ typedef struct _BoardDims { /* tray */ XP_U16 trayLeft, trayTop, trayWidth, trayHt; + XP_U16 traySize; /* other */ XP_U16 cellSize, maxCellSize; @@ -116,7 +117,7 @@ void board_setScoreboardLoc( BoardCtxt* board, XP_Bool divideHorizontally ); void board_setTrayLoc( BoardCtxt* board, XWEnv xwe, XP_U16 trayLeft, XP_U16 trayTop, - XP_U16 trayWidth, XP_U16 trayHeight ); + XP_U16 trayWidth, XP_U16 trayHeight, XP_U16 nTiles ); /* Vertical scroll support; offset is in rows, not pixels */ XP_Bool board_setYOffset( BoardCtxt* board, XWEnv xwe, XP_U16 newOffset ); diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 65a837d5e..57fd823a0 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -47,6 +47,7 @@ #endif #define MAX_COLS MAX_ROWS +#define STREAM_VERS_NINETILES 0x1E #define STREAM_VERS_NOEMPTYDICT 0x1D #define STREAM_VERS_GICREATED 0x1C /* game struct gets created timestamp */ #define STREAM_VERS_DUPLICATE 0x1B @@ -91,7 +92,7 @@ #define STREAM_VERS_405 0x01 /* search for FIX_NEXT_VERSION_CHANGE next time this is changed */ -#define CUR_STREAM_VERS STREAM_VERS_NOEMPTYDICT +#define CUR_STREAM_VERS STREAM_VERS_NINETILES typedef struct XP_Rect { XP_S16 left; @@ -189,15 +190,12 @@ typedef enum { } XWTimerReason; #define MAX_NUM_PLAYERS 4 -#ifdef EIGHT_TILES -# define MAX_TRAY_TILES 8 -#else -# define MAX_TRAY_TILES 7 -#endif +#define MIN_TRAY_TILES 7 +#define MAX_TRAY_TILES 9 #define PLAYERNUM_NBITS 2 #define NDEVICES_NBITS 2 /* 1-4, but reduced by 1 fits in 2 bits */ #define NPLAYERS_NBITS 3 -#define EMPTIED_TRAY_BONUS 50 +#define BINGO_BONUS 50 #if MAX_ROWS <= 16 typedef XP_U16 RowFlags; @@ -293,7 +291,7 @@ typedef struct _MoveInfoTile { Tile tile; /* 6 bits will do */ } MoveInfoTile; -typedef struct MoveInfo { +typedef struct _MoveInfo { XP_U8 nTiles; /* 4 bits: 0-7 */ XP_U8 commonCoord; /* 5 bits: 0-16 if 17x17 possible */ XP_Bool isHorizontal; /* 1 bit */ diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c index 02c0af192..93d9ede00 100644 --- a/xwords4/common/engine.c +++ b/xwords4/common/engine.c @@ -576,8 +576,7 @@ static void findMovesOneRow( EngineCtxt* engine, XWEnv xwe ) { XP_U16 lastCol = engine->numCols - 1; - XP_U16 col, row = engine->curRow; - XP_S16 prevAnchor; + XP_U16 row = engine->curRow; XP_U16 firstSearchCol, lastSearchCol; #ifdef XWFEATURE_SEARCHLIMIT const BdHintLimits* searchLimits = engine->searchLimits; @@ -600,7 +599,7 @@ findMovesOneRow( EngineCtxt* engine, XWEnv xwe ) } XP_MEMSET( &engine->rowChecks, 0, sizeof(engine->rowChecks) ); /* clear */ - for ( col = 0; col <= lastCol; ++col ) { + for ( XP_U16 col = 0; col <= lastCol; ++col ) { if ( col < firstSearchCol || col > lastSearchCol ) { engine->scoreCache[col] = 0; } else { @@ -610,8 +609,8 @@ findMovesOneRow( EngineCtxt* engine, XWEnv xwe ) } } - prevAnchor = firstSearchCol - 1; - for ( col = firstSearchCol; col <= lastSearchCol && !engine->returnNOW; + XP_S16 prevAnchor = firstSearchCol - 1; + for ( XP_U16 col = firstSearchCol; col <= lastSearchCol && !engine->returnNOW; ++col ) { if ( isAnchorSquare( engine, col, row ) ) { findMovesForAnchor( engine, xwe, &prevAnchor, col, row ); diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 57e60c50a..29ad0cbca 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -649,6 +649,8 @@ gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI ) destGI->nPlayers = (XP_U8)srcGI->nPlayers; nPlayers = srcGI->nPlayers; destGI->boardSize = (XP_U8)srcGI->boardSize; + destGI->traySize = srcGI->traySize; + destGI->bingoMin = srcGI->bingoMin; destGI->serverRole = srcGI->serverRole; destGI->hintsNotAllowed = srcGI->hintsNotAllowed; @@ -748,6 +750,12 @@ gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ) gi->nPlayers = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); gi->boardSize = (XP_U8)stream_getBits( stream, nColsNBits ); + if ( STREAM_VERS_NINETILES <= strVersion ) { + gi->traySize = (XP_U8)stream_getBits( stream, NTILES_NBITS_9 ); + gi->bingoMin = (XP_U8)stream_getBits( stream, NTILES_NBITS_9 ); + } else { + gi->traySize = gi->bingoMin = 7; + } gi->serverRole = (DeviceRole)stream_getBits( stream, 2 ); /* XP_LOGF( "%s: read serverRole of %d", __func__, gi->serverRole ); */ gi->hintsNotAllowed = stream_getBits( stream, 1 ); @@ -830,6 +838,14 @@ gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi ) stream_putBits( stream, NPLAYERS_NBITS, gi->nPlayers ); stream_putBits( stream, nColsNBits, gi->boardSize ); + + if ( STREAM_VERS_NINETILES <= strVersion ) { + XP_ASSERT( 0 < gi->traySize ); + stream_putBits( stream, NTILES_NBITS_9, gi->traySize ); + stream_putBits( stream, NTILES_NBITS_9, gi->bingoMin ); + } else { + XP_LOGFF( "strVersion: %d so not writing traySize", strVersion ); + } stream_putBits( stream, 2, gi->serverRole ); stream_putBits( stream, 1, gi->hintsNotAllowed ); stream_putBits( stream, 2, gi->phoniesAction ); diff --git a/xwords4/common/gameinfo.h b/xwords4/common/gameinfo.h index 3d5a3ce96..c7e6e67a8 100644 --- a/xwords4/common/gameinfo.h +++ b/xwords4/common/gameinfo.h @@ -51,6 +51,8 @@ typedef struct CurGameInfo { XP_LangCode dictLang; XP_U8 nPlayers; XP_U8 boardSize; + XP_U8 traySize; + XP_U8 bingoMin; XP_U8 forceChannel; DeviceRole serverRole; diff --git a/xwords4/common/model.c b/xwords4/common/model.c index ced1398bd..016355898 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -123,7 +123,6 @@ model_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, { ModelCtxt* model; XP_U16 nCols; - XP_U16 nPlayers; XP_U16 version = stream_getVersion( stream ); XP_ASSERT( !!dict || !!dicts ); @@ -139,7 +138,7 @@ model_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, } XP_ASSERT( MAX_COLS >= nCols ); - nPlayers = (XP_U16)stream_getBits( stream, NPLAYERS_NBITS ); + XP_U16 nPlayers = (XP_U16)stream_getBits( stream, NPLAYERS_NBITS ); model = model_make( MPPARM(mpool) xwe, dict, dicts, util, nCols ); model->nPlayers = nPlayers; @@ -161,7 +160,6 @@ model_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, stack_loadFromStream( model->vol.stack, stream ); MovePrintFuncPre pre = NULL; - MovePrintFuncPost post = NULL; void* closure = NULL; #ifdef DEBUG pre = assertDiffTurn; @@ -171,7 +169,7 @@ model_makeFromStream( MPFORMAL XWEnv xwe, XWStreamCtxt* stream, buildModelFromStack( model, xwe, model->vol.stack, XP_FALSE, 0, (XWStreamCtxt*)NULL, (WordNotifierInfo*)NULL, - pre, post, closure ); + pre, (MovePrintFuncPost)NULL, closure ); for ( int ii = 0; ii < model->nPlayers; ++ii ) { loadPlayerCtxt( model, stream, version, &model->players[ii] ); @@ -284,6 +282,12 @@ model_setSize( ModelCtxt* model, XP_U16 nCols ) } } /* model_setSize */ +void +model_forceStack7Tiles( ModelCtxt* model ) +{ + stack_set7Tiles( model->vol.stack ); +} + void model_destroy( ModelCtxt* model, XWEnv xwe ) { @@ -1139,7 +1143,7 @@ model_currentMoveToStream( ModelCtxt* model, XP_S16 turn, XP_ASSERT( turn >= 0 ); XP_S16 numTiles = model->players[turn].nPending; - stream_putBits( stream, NTILES_NBITS, numTiles ); + stream_putBits( stream, tilesNBits(stream), numTiles ); while ( numTiles-- ) { Tile tile; @@ -1177,8 +1181,8 @@ model_makeTurnFromStream( ModelCtxt* model, XWEnv xwe, XP_U16 playerNum, model_resetCurrentTurn( model, xwe, playerNum ); - XP_U16 numTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS ); - XP_LOGF( "%s: numTiles=%d", __func__, numTiles ); + XP_U16 numTiles = (XP_U16)stream_getBits( stream, tilesNBits(stream) ); + XP_LOGFF( "numTiles=%d", numTiles ); Tile tileFaces[numTiles]; XP_U16 cols[numTiles]; @@ -2141,17 +2145,17 @@ model_getNumTilesInTray( ModelCtxt* model, XP_S16 turn ) XP_ASSERT( turn >= 0 ); player = &model->players[turn]; XP_U16 result = player->trayTiles.nTiles; - // XP_LOGF( "%s(turn=%d) => %d", __func__, turn, result ); + // XP_LOGFF( "(turn=%d) => %d", turn, result ); return result; } /* model_getNumTilesInTray */ XP_U16 model_getNumTilesTotal( ModelCtxt* model, XP_S16 turn ) { - PlayerCtxt* player; XP_ASSERT( turn >= 0 ); - player = &model->players[turn]; - return player->trayTiles.nTiles + player->nPending; + PlayerCtxt* player = &model->players[turn]; + XP_U16 result = player->trayTiles.nTiles + player->nPending; + return result; } /* model_getNumTilesTotal */ XP_U16 @@ -2423,8 +2427,10 @@ copyStack( const ModelCtxt* model, XWEnv xwe, StackCtxt* destStack, mem_stream_make_raw( MPPARM(model->vol.mpool) dutil_getVTManager(model->vol.dutil) ); - stack_writeToStream( (StackCtxt*)srcStack, stream ); + stream_setVersion( stream, stack_getVersion(srcStack) ); + stack_writeToStream( srcStack, stream ); stack_loadFromStream( destStack, stream ); + XP_ASSERT( stack_getVersion(destStack) == stack_getVersion( srcStack ) ); stream_destroy( stream, xwe ); } /* copyStack */ @@ -2758,8 +2764,6 @@ static void loadPlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, XP_U16 version, PlayerCtxt* pc ) { - PendingTile* pt; - XP_U16 nTiles; XP_U16 nColsNBits; #ifdef STREAM_VERS_BIGBOARD nColsNBits = 16 <= model_numCols( model ) ? NUMCOLS_NBITS_5 @@ -2772,25 +2776,25 @@ loadPlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, XP_U16 version, pc->curMoveValid = stream_getBits( stream, 1 ); traySetFromStream( stream, &pc->trayTiles ); - - pc->nPending = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + + const XP_U16 nTileBits = tilesNBits(stream); + pc->nPending = (XP_U8)stream_getBits( stream, nTileBits ); if ( STREAM_VERS_NUNDONE <= version ) { - pc->nUndone = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + pc->nUndone = (XP_U8)stream_getBits( stream, nTileBits ); } else { XP_ASSERT( 0 == pc->nUndone ); } XP_ASSERT( 0 == pc->dividerLoc ); if ( STREAM_VERS_MODELDIVIDER <= version ) { - pc->dividerLoc = stream_getBits( stream, NTILES_NBITS ); + pc->dividerLoc = stream_getBits( stream, nTileBits ); } - nTiles = pc->nPending + pc->nUndone; - for ( pt = pc->pendingTiles; nTiles-- > 0; ++pt ) { - XP_U16 nBits; + XP_U16 nTiles = pc->nPending + pc->nUndone; + for ( PendingTile* pt = pc->pendingTiles; nTiles-- > 0; ++pt ) { pt->col = (XP_U8)stream_getBits( stream, nColsNBits ); pt->row = (XP_U8)stream_getBits( stream, nColsNBits ); - nBits = (version <= STREAM_VERS_RELAY) ? 6 : 7; + XP_U16 nBits = (version <= STREAM_VERS_RELAY) ? 6 : 7; pt->tile = (Tile)stream_getBits( stream, nBits ); } @@ -2814,10 +2818,11 @@ writePlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, stream_putBits( stream, 1, pc->curMoveValid ); traySetToStream( stream, &pc->trayTiles ); - - stream_putBits( stream, NTILES_NBITS, pc->nPending ); - stream_putBits( stream, NTILES_NBITS, pc->nUndone ); - stream_putBits( stream, NTILES_NBITS, pc->dividerLoc ); + + XP_U16 nBits = tilesNBits( stream ); + stream_putBits( stream, nBits, pc->nPending ); + stream_putBits( stream, nBits, pc->nUndone ); + stream_putBits( stream, nBits, pc->dividerLoc ); nTiles = pc->nPending + pc->nUndone; for ( pt = pc->pendingTiles; nTiles-- > 0; ++pt ) { diff --git a/xwords4/common/model.h b/xwords4/common/model.h index bdd7e198c..f6a166a96 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -35,11 +35,8 @@ extern "C" { # define NUMCOLS_NBITS_5 5 #endif -#ifdef EIGHT_TILES -# define NTILES_NBITS 4 -#else -# define NTILES_NBITS 3 -#endif +#define NTILES_NBITS_7 3 +#define NTILES_NBITS_9 4 /* apply to CellTile */ #define TILE_VALUE_MASK 0x003F @@ -63,7 +60,7 @@ typedef struct BlankQueue { XP_U8 row[MAX_NUM_BLANKS]; } BlankQueue; -typedef XP_U8 TileBit; /* bits indicating selection of tiles in tray */ +typedef XP_U16 TileBit; /* bits indicating selection of tiles in tray */ #define ALLTILES ((TileBit)~(0xFF<<(MAX_TRAY_TILES))) #define ILLEGAL_MOVE_SCORE (-1) @@ -88,6 +85,7 @@ void model_writeToTextStream( const ModelCtxt* model, XWStreamCtxt* stream ); #endif void model_setSize( ModelCtxt* model, XP_U16 boardSize ); +void model_forceStack7Tiles( ModelCtxt* model ); void model_destroy( ModelCtxt* model, XWEnv xwe ); XP_U32 model_getHash( const ModelCtxt* model ); XP_Bool model_hashMatches( const ModelCtxt* model, XP_U32 hash ); diff --git a/xwords4/common/modelp.h b/xwords4/common/modelp.h index fb84f38e6..a9bb2c058 100644 --- a/xwords4/common/modelp.h +++ b/xwords4/common/modelp.h @@ -34,7 +34,7 @@ typedef struct PendingTile { Tile tile; /* includes face and blank bit */ } PendingTile; -typedef struct PlayerCtxt { +typedef struct _PlayerCtxt { XP_S16 score; XP_S16 curMoveScore; /* negative means illegal */ XP_Bool curMoveValid; diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c index c52163dda..8d4ea1620 100644 --- a/xwords4/common/movestak.c +++ b/xwords4/common/movestak.c @@ -59,6 +59,7 @@ struct StackCtxt { }; #define HAVE_FLAGS_MASK ((XP_U16)0x8000) +#define VERS_7TILES_BIT 0x01 static XP_Bool popEntryImpl( StackCtxt* stack, StackEntry* entry ); @@ -74,6 +75,21 @@ stack_init( StackCtxt* stack, XP_U16 nPlayers, XP_Bool inDuplicateMode ) shrunk to fit as soon as we serialize/deserialize anyway. */ } /* stack_init */ + +void +stack_set7Tiles( StackCtxt* stack ) +{ + XP_ASSERT( !stack->data ); + stack->flags |= VERS_7TILES_BIT; +} + +XP_U16 +stack_getVersion( const StackCtxt* stack ) +{ + XP_ASSERT( !!stack->data ); + return stream_getVersion( stack->data ); +} + #ifdef STREAM_VERS_HASHSTREAM XP_U32 stack_getHash( const StackCtxt* stack ) @@ -143,12 +159,18 @@ stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream ) nBytes &= ~HAVE_FLAGS_MASK; if ( nBytes > 0 ) { + XP_U8 stackVersion = STREAM_VERS_NINETILES - 1; + if ( STREAM_VERS_NINETILES <= stream_getVersion(stream) ) { + stackVersion = stream_getU8( stream ); + XP_LOGFF( "read stackVersion: %d from stream", stackVersion ); + } stack->highWaterMark = stream_getU16( stream ); stack->nEntries = stream_getU16( stream ); stack->top = stream_getU32( stream ); stack->data = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr ); stream_getFromStream( stack->data, stream, nBytes ); + stream_setVersion( stack->data, stackVersion ); } else { XP_ASSERT( stack->nEntries == 0 ); XP_ASSERT( stack->top == 0 ); @@ -178,6 +200,9 @@ stack_writeToStream( const StackCtxt* stack, XWStreamCtxt* stream ) } if ( nBytes > 0 ) { + if ( STREAM_VERS_NINETILES <= stream_getVersion(stream) ) { + stream_putU8( stream, stream_getVersion(data) ); + } stream_putU16( stream, stack->highWaterMark ); stream_putU16( stream, stack->nEntries ); stream_putU32( stream, stack->top ); @@ -208,16 +233,18 @@ stack_copy( const StackCtxt* stack ) static void pushEntryImpl( StackCtxt* stack, const StackEntry* entry ) { - XWStreamCtxt* stream = stack->data; - XP_LOGFF( "(typ=%s, player=%d)", StackMoveType_2str(entry->moveType), entry->playerNum ); + XWStreamCtxt* stream = stack->data; if ( !stream ) { - stream = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr ); - stack->data = stream; + stream = stack->data = + mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr ); + XP_U16 version = 0 == (stack->flags & VERS_7TILES_BIT) + ? CUR_STREAM_VERS : STREAM_VERS_NINETILES - 1; + stream_setVersion( stream, version ); stack->typeBits = stack->inDuplicateMode ? 3 : 2; /* the new size */ - XP_ASSERT( 0 == stack->flags ); + XP_ASSERT( 0 == (~VERS_7TILES_BIT & stack->flags) ); } XWStreamPos oldLoc = stream_setPos( stream, POS_WRITE, stack->top ); diff --git a/xwords4/common/movestak.h b/xwords4/common/movestak.h index e2a539dcd..9d14c6ee0 100644 --- a/xwords4/common/movestak.h +++ b/xwords4/common/movestak.h @@ -65,7 +65,7 @@ typedef struct _PauseRec { const XP_UCHAR* msg; /* requires stack_freeEntry() */ } PauseRec; -typedef union EntryData { +typedef union _EntryData { AssignRec assign; TradeRec trade; MoveRec move; @@ -73,7 +73,7 @@ typedef union EntryData { PauseRec pause; } EntryData; -typedef struct StackEntry { +typedef struct _StackEntry { StackMoveType moveType; XP_U8 playerNum; XP_U8 moveNum; @@ -86,6 +86,8 @@ StackCtxt* stack_make( MPFORMAL VTableMgr* vtmgr, XP_U16 nPlayers, XP_Bool inDup void stack_destroy( StackCtxt* stack ); void stack_init( StackCtxt* stack, XP_U16 nPlayers, XP_Bool inDuplicateMode ); +void stack_set7Tiles( StackCtxt* stack ); +XP_U16 stack_getVersion( const StackCtxt* stack ); XP_U32 stack_getHash( const StackCtxt* stack ); void stack_setBitsPerTile( StackCtxt* stack, XP_U16 bitsPerTile ); diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 1835f027c..2fbc18cf3 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -527,9 +527,9 @@ isLegalMove( ModelCtxt* model, XWEnv xwe, MoveInfo* mInfo, XP_Bool silent ) } /* isLegalMove */ XP_U16 -figureMoveScore( const ModelCtxt* model, XWEnv xwe, XP_U16 turn, const MoveInfo* moveInfo, - EngineCtxt* engine, XWStreamCtxt* stream, - WordNotifierInfo* notifyInfo ) +figureMoveScore( const ModelCtxt* model, XWEnv xwe, XP_U16 turn, + const MoveInfo* moveInfo, EngineCtxt* engine, + XWStreamCtxt* stream, WordNotifierInfo* notifyInfo ) { XP_U16 col, row; XP_U16* incr; @@ -584,13 +584,22 @@ figureMoveScore( const ModelCtxt* model, XWEnv xwe, XP_U16 turn, const MoveInfo* score += oneScore; } + const CurGameInfo* gi = model->vol.gi; + /* did he use all 7 tiles? */ - if ( nTiles == MAX_TRAY_TILES ) { - score += EMPTIED_TRAY_BONUS; + if ( gi->bingoMin <= nTiles ) { + score += BINGO_BONUS; if ( !!stream ) { - const XP_UCHAR* bstr = dutil_getUserString( model->vol.dutil, - xwe, STR_BONUS_ALL ); + const XP_UCHAR* bstr; + XP_UCHAR buf[128]; + if ( gi->bingoMin == gi->traySize ) { + bstr = dutil_getUserString( model->vol.dutil, xwe, STR_BONUS_ALL ); + } else { + bstr = dutil_getUserString( model->vol.dutil, xwe, STR_BONUS_ALL_SUB ); + XP_SNPRINTF( buf, VSIZE(buf), bstr, gi->bingoMin ); + bstr = buf; + } stream_catString( stream, bstr ); } } diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 34402af5a..8eaeacf6f 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -1,6 +1,6 @@ /* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */ /* - * Copyright 1997 - 2020 by Eric House (xwords@eehouse.org). All rights + * Copyright 1997 - 2021 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -912,6 +912,15 @@ setStreamVersion( ServerCtxt* server ) } XP_LOGF( "%s: setting streamVersion: 0x%x", __func__, streamVersion ); server->nv.streamVersion = streamVersion; + + CurGameInfo* gi = server->vol.gi; + if ( STREAM_VERS_NINETILES > streamVersion ) { + if ( 7 < gi->traySize ) { + XP_LOGFF( "reducing tray size from %d to 7", gi->traySize ); + gi->traySize = gi->bingoMin = 7; + } + model_forceStack7Tiles( server->vol.model ); + } } static void @@ -1919,6 +1928,9 @@ client_readInitialMessage( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream ) XP_U8 streamVersion = stream_getU8( stream ); XP_LOGF( "%s: set streamVersion to %d", __func__, streamVersion ); stream_setVersion( stream, streamVersion ); + if ( STREAM_VERS_NINETILES > streamVersion ) { + model_forceStack7Tiles( server->vol.model ); + } // XP_ASSERT( streamVersion <= CUR_STREAM_VERS ); /* else do what? */ gameID = stream_getU32( stream ); @@ -1934,8 +1946,6 @@ client_readInitialMessage( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream ) localGI.dictName = copyString( server->mpool, gi->dictName ); gi_copy( MPPARM(server->mpool) gi, &localGI ); - XP_U16 nCols = localGI.boardSize; - if ( streamVersion < STREAM_VERS_NOEMPTYDICT ) { XP_LOGFF( "loading and dropping empty dict" ); DictionaryCtxt* empty = util_makeEmptyDict( server->vol.util, xwe ); @@ -1957,7 +1967,7 @@ client_readInitialMessage( ServerCtxt* server, XWEnv xwe, XWStreamCtxt* stream ) server->nv.addresses[0].channelNo = channelNo; XP_LOGF( "%s: assigning channelNo %x for 0", __func__, channelNo ); - model_setSize( model, nCols ); + model_setSize( model, localGI.boardSize ); XP_U16 nPlayers = localGI.nPlayers; XP_LOGF( "%s: reading in %d players", __func__, localGI.nPlayers ); @@ -2583,8 +2593,8 @@ assignTilesToAll( ServerCtxt* server, XWEnv xwe ) model_setNPlayers( model, nPlayers ); numAssigned = pool_getNTilesLeft( server->pool ) / nPlayers; - if ( numAssigned > MAX_TRAY_TILES ) { - numAssigned = MAX_TRAY_TILES; + if ( numAssigned > gi->traySize ) { + numAssigned = gi->traySize; } /* Loop through all the players. If picking tiles is on, stop for each diff --git a/xwords4/common/strutils.c b/xwords4/common/strutils.c index a4eb86b5a..f424747a6 100644 --- a/xwords4/common/strutils.c +++ b/xwords4/common/strutils.c @@ -53,7 +53,7 @@ void traySetToStream( XWStreamCtxt* stream, const TrayTileSet* ts ) { XP_U16 nTiles = ts->nTiles; - stream_putBits( stream, NTILES_NBITS, nTiles ); + stream_putBits( stream, tilesNBits(stream), nTiles ); tilesToStream( stream, ts->tiles, nTiles ); } /* traySetFromStream */ @@ -99,7 +99,7 @@ scoresFromStream( XWStreamCtxt* stream, XP_U16 nScores, XP_U16* scores ) void traySetFromStream( XWStreamCtxt* stream, TrayTileSet* ts ) { - XP_U16 nTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS ); + XP_U16 nTiles = (XP_U16)stream_getBits( stream, tilesNBits( stream ) ); tilesFromStream( stream, ts->tiles, nTiles ); ts->nTiles = (XP_U8)nTiles; } /* traySetFromStream */ @@ -123,7 +123,7 @@ moveInfoToStream( XWStreamCtxt* stream, const MoveInfo* mi, XP_U16 bitsPerTile ) #endif assertSorted( mi ); - stream_putBits( stream, NTILES_NBITS, mi->nTiles ); + stream_putBits( stream, tilesNBits( stream ), mi->nTiles ); stream_putBits( stream, NUMCOLS_NBITS_5, mi->commonCoord ); stream_putBits( stream, 1, mi->isHorizontal ); @@ -148,7 +148,7 @@ moveInfoFromStream( XWStreamCtxt* stream, MoveInfo* mi, XP_U16 bitsPerTile ) /* XP_UCHAR buf[64] = {0}; */ /* XP_U16 offset = 0; */ #endif - mi->nTiles = stream_getBits( stream, NTILES_NBITS ); + mi->nTiles = stream_getBits( stream, tilesNBits( stream ) ); XP_ASSERT( mi->nTiles <= MAX_TRAY_TILES ); mi->commonCoord = stream_getBits( stream, NUMCOLS_NBITS_5 ); mi->isHorizontal = stream_getBits( stream, 1 ); @@ -396,6 +396,19 @@ finishHash( XP_U32 hash ) return hash; } +XP_U16 +tilesNBits( const XWStreamCtxt* stream ) +{ + XP_U16 version = stream_getVersion( stream ); + XP_ASSERT( 0 < version ); + if ( 0 == version ) { + XP_LOGFF( "version is 0" ); + } + XP_U16 result = STREAM_VERS_NINETILES <= version + ? NTILES_NBITS_9 : NTILES_NBITS_7; + return result; +} + const XP_UCHAR* lcToLocale( XP_LangCode lc ) { diff --git a/xwords4/common/strutils.h b/xwords4/common/strutils.h index aba605644..1ce35e482 100644 --- a/xwords4/common/strutils.h +++ b/xwords4/common/strutils.h @@ -86,6 +86,8 @@ void insetRect( XP_Rect* rect, XP_S16 byWidth, XP_S16 byHeight ); XP_U32 augmentHash( XP_U32 hash, const XP_U8* ptr, XP_U16 len ); XP_U32 finishHash( XP_U32 hash ); +XP_U16 tilesNBits( const XWStreamCtxt* stream ); + const XP_UCHAR* lcToLocale( XP_LangCode lc ); void p_replaceStringIfDifferent( MPFORMAL XP_UCHAR** curLoc, diff --git a/xwords4/common/tray.c b/xwords4/common/tray.c index 76c8927c2..e3d4fbacd 100644 --- a/xwords4/common/tray.c +++ b/xwords4/common/tray.c @@ -132,8 +132,6 @@ figureTrayTileRect( BoardCtxt* board, XP_U16 index, XP_Rect* rect ) void drawTray( BoardCtxt* board, XWEnv xwe ) { - XP_Rect tileRect; - if ( (board->trayInvalBits != 0) || board->dividerInvalid ) { const XP_S16 turn = board->selPlayer; PerTurnInfo* pti = board->selInfo; @@ -165,9 +163,9 @@ drawTray( BoardCtxt* board, XWEnv xwe ) if ( dictionary != NULL ) { XP_Bool showFaces = board->trayVisState == TRAY_REVEALED; Tile blank = dict_getBlankTile( dictionary ); + const XP_U16 nTrayTiles = board->gi->traySize; if ( turn >= 0 ) { - XP_S16 ii; /* which tile slot are we drawing in */ XP_U16 ddAddedIndx, ddRmvdIndx; XP_U16 numInTray = countTilesToShow( board ); XP_Bool isBlank; @@ -179,7 +177,7 @@ drawTray( BoardCtxt* board, XWEnv xwe ) /* draw in reverse order so drawing happens after erasing */ - for ( ii = MAX_TRAY_TILES - 1; ii >= 0; --ii ) { + for ( int ii = nTrayTiles - 1; ii >= 0; --ii ) { CellFlags flags = baseFlags; XP_U16 mask = 1 << ii; @@ -191,6 +189,7 @@ drawTray( BoardCtxt* board, XWEnv xwe ) flags |= CELL_ISCURSOR; } #endif + XP_Rect tileRect; figureTrayTileRect( board, ii, &tileRect ); XP_Bool drew; @@ -262,7 +261,7 @@ drawTray( BoardCtxt* board, XWEnv xwe ) board->dividerInvalid = XP_FALSE; } drawPendingScore( board, xwe, turnScore, - (cursorBits & (1<<(MAX_TRAY_TILES-1))) != 0); + (cursorBits & (1<<(nTrayTiles - 1))) != 0); } draw_objFinished( board->draw, xwe, OBJ_TRAY, &board->trayBounds, @@ -336,12 +335,13 @@ static void drawPendingScore( BoardCtxt* board, XWEnv xwe, XP_S16 turnScore, XP_Bool hasCursor ) { /* Draw the pending score down in the last tray's rect */ - if ( countTilesToShow( board ) < MAX_TRAY_TILES ) { + XP_U16 traySize = board->gi->traySize; + if ( countTilesToShow( board ) < traySize ) { XP_U16 selPlayer = board->selPlayer; XP_Bool curTurn = server_isPlayersTurn( board->server, selPlayer ); XP_Rect lastTileR; - figureTrayTileRect( board, MAX_TRAY_TILES-1, &lastTileR ); + figureTrayTileRect( board, traySize - 1, &lastTileR ); if ( 0 < lastTileR.width && 0 < lastTileR.height ) { draw_score_pendingScore( board->draw, xwe, &lastTileR, turnScore, selPlayer, curTurn, @@ -368,10 +368,9 @@ invalTilesUnderRect( BoardCtxt* board, const XP_Rect* rect ) it for now. If it needs to be faster, invalCellsUnderRect is the model to use. */ - XP_U16 ii; XP_Rect locRect; - for ( ii = 0; ii < MAX_TRAY_TILES; ++ii ) { + for ( int ii = 0; ii < board->gi->traySize; ++ii ) { figureTrayTileRect( board, ii, &locRect ); if ( rectsIntersect( rect, &locRect ) ) { board_invalTrayTiles( board, (TileBit)(1 << ii) ); @@ -458,7 +457,7 @@ handleActionInTray( BoardCtxt* board, XWEnv xwe, XP_S16 index, XP_Bool onDivider result = XP_TRUE; } #endif - } else if ( index == -(MAX_TRAY_TILES) ) { /* pending score tile */ + } else if ( index == -(board->gi->traySize) ) { /* pending score tile */ result = board_commitTurn( board, xwe, XP_FALSE, XP_FALSE, NULL ); #if defined XWFEATURE_TRAYUNDO_ALL } else if ( index < 0 ) { /* other empty area */ @@ -535,7 +534,8 @@ void invalTrayTilesAbove( BoardCtxt* board, XP_U16 tileIndex ) { TileBit bits = 0; - while ( tileIndex < MAX_TRAY_TILES ) { + const XP_U16 traySize = board->gi->traySize; + while ( tileIndex < traySize ) { bits |= 1 << tileIndex++; } board_invalTrayTiles( board, bits ); @@ -626,16 +626,17 @@ tray_moveCursor( BoardCtxt* board, XP_Key cursorKey, XP_Bool preflightOnly, PerTurnInfo* pti = board->selInfo; XP_S16 trayCursorLoc; XP_S16 newLoc; + const XP_U16 traySize = board->gi->traySize; for ( ; ; ) { trayCursorLoc = pti->trayCursorLoc; newLoc = trayCursorLoc + delta; - if ( newLoc < 0 || newLoc > MAX_TRAY_TILES ) { + if ( newLoc < 0 || newLoc > traySize ) { up = XP_TRUE; } else if ( !preflightOnly ) { XP_S16 tileLoc = trayCursorLoc; XP_U16 nTiles = board->trayVisState == TRAY_REVEALED ? model_getNumTilesInTray( board->model, selPlayer ) - : MAX_TRAY_TILES; + : traySize; XP_U16 dividerLoc = getDividerLoc( board ); XP_Bool cursorOnDivider = trayCursorLoc == dividerLoc; XP_Bool cursorObjSelected; @@ -683,7 +684,7 @@ tray_moveCursor( BoardCtxt* board, XP_Key cursorKey, XP_Bool preflightOnly, if ( (newTileLoc > nTiles) && (newLoc != dividerLoc) - && (newTileLoc < MAX_TRAY_TILES-1) ) { + && (newTileLoc < traySize-1) ) { continue; } } @@ -730,9 +731,10 @@ board_moveDivider( BoardCtxt* board, XP_Bool right ) XP_Bool result = board->trayVisState == TRAY_REVEALED; if ( result ) { XP_U8 loc = getDividerLoc( board ); - loc += MAX_TRAY_TILES + 1; + const XP_U16 traySize = board->gi->traySize; + loc += traySize + 1; loc += right? 1:-1; - loc %= MAX_TRAY_TILES + 1; + loc %= traySize + 1; (void)dividerMoved( board, loc ); } diff --git a/xwords4/linux/LocalizedStrIncludes.h b/xwords4/linux/LocalizedStrIncludes.h index 30bcb2a2e..315d9d2fa 100644 --- a/xwords4/linux/LocalizedStrIncludes.h +++ b/xwords4/linux/LocalizedStrIncludes.h @@ -59,6 +59,8 @@ enum { STRD_DUP_TRADED, STRSD_DUP_ONESCORE, + STR_BONUS_ALL_SUB, + /* These three aren't in Android */ STR_LOCALPLAYERS, STR_TOTALPLAYERS, diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index a915afb08..0350a727f 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -1,6 +1,6 @@ /* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2001-2013 by Eric House (xwords@eehouse.org). All rights + * Copyright 2001 - 2021 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -28,9 +28,13 @@ #include "nwgamest.h" #include "gtkconnsdlg.h" #include "gtkutils.h" +#include "gtkask.h" #define MAX_SIZE_CHOICES 32 +#define BINGO_THRESHOLD "Bingo threshold" +#define TRAY_SIZE "Tray size" + typedef struct GtkNewGameState { GtkGameGlobals* globals; CurGameInfo* gi; @@ -46,6 +50,8 @@ typedef struct GtkNewGameState { XP_Bool fireConnDlg; gboolean isNewGame; short nCols; /* for board size */ + int nTrayTiles; + int bingoMin; gchar* dict; #ifndef XWFEATURE_STANDALONE_ONLY @@ -167,6 +173,26 @@ size_combo_changed( GtkComboBox* combo, gpointer gp ) } } /* size_combo_changed */ +static void +tray_size_changed( GtkComboBox* combo, gpointer gp ) +{ + GtkNewGameState* state = (GtkNewGameState*)gp; + gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); + if ( index >= 0 ) { + state->nTrayTiles = 7 + index; + } +} + +static void +bingo_changed( GtkComboBox* combo, gpointer gp ) +{ + GtkNewGameState* state = (GtkNewGameState*)gp; + gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); + if ( index >= 0 ) { + state->bingoMin = 7 + index; + } +} + static void dict_combo_changed( GtkComboBox* combo, gpointer gp ) { @@ -278,6 +304,103 @@ addDuplicateCheckbox( GtkNewGameState* state, GtkWidget* parent ) gtk_box_pack_start( GTK_BOX(parent), duplicateCheck, FALSE, TRUE, 0 ); } +static void +addSizesRow( GtkNewGameState* state, GtkWidget* parent ) +{ + LOG_FUNC(); + GtkWidget* hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); + + /* Tray size */ + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new(TRAY_SIZE ":"), FALSE, TRUE, 0 ); + GtkWidget* traySizeCombo = gtk_combo_box_text_new(); + for ( int ii = MIN_TRAY_TILES; ii <= MAX_TRAY_TILES; ++ii ) { + char buf[10]; + snprintf( buf, sizeof(buf), "%d", ii ); + gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(traySizeCombo), buf ); + } + gtk_combo_box_set_active( GTK_COMBO_BOX(traySizeCombo), + state->nTrayTiles - MIN_TRAY_TILES ); + + g_signal_connect( traySizeCombo, "changed", G_CALLBACK(tray_size_changed), state ); + gtk_widget_show( traySizeCombo ); + gtk_box_pack_start( GTK_BOX(hbox), traySizeCombo, FALSE, TRUE, 0 ); + + /* Bingo threshold */ + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new(BINGO_THRESHOLD":"), FALSE, TRUE, 0 ); + GtkWidget* bingoCombo = gtk_combo_box_text_new(); + for ( int ii = MIN_TRAY_TILES; ii <= MAX_TRAY_TILES; ++ii ) { + char buf[10]; + snprintf( buf, sizeof(buf), "%d", ii ); + gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(bingoCombo), buf ); + } + gtk_combo_box_set_active( GTK_COMBO_BOX(bingoCombo), state->bingoMin - MIN_TRAY_TILES ); + + g_signal_connect( bingoCombo, "changed", G_CALLBACK(bingo_changed), state ); + gtk_widget_show( bingoCombo ); + gtk_box_pack_start( GTK_BOX(hbox), bingoCombo, FALSE, TRUE, 0 ); + + /* board size choices */ + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Board size:"), + FALSE, TRUE, 0 ); + + GtkWidget* boardSizeCombo = gtk_combo_box_text_new(); + if ( !state->isNewGame ) { + gtk_widget_set_sensitive( boardSizeCombo, FALSE ); + } + + for ( int ii = 0; ii < MAX_SIZE_CHOICES; ++ii ) { + char buf[10]; + XP_U16 siz = MAX_COLS - ii; + snprintf( buf, sizeof(buf), "%dx%d", siz, siz ); + gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(boardSizeCombo), buf ); + if ( siz == state->nCols ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(boardSizeCombo), ii ); + } + } + + g_signal_connect( boardSizeCombo, "changed", + G_CALLBACK(size_combo_changed), state ); + + gtk_widget_show( boardSizeCombo ); + gtk_box_pack_start( GTK_BOX(hbox), boardSizeCombo, FALSE, TRUE, 0 ); + + gtk_box_pack_start( GTK_BOX(parent), hbox, FALSE, TRUE, 0 ); +} /* addSizesRow */ + +static void +addDictsRow( GtkNewGameState* state, GtkWidget* parent ) +{ + GtkWidget* hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); + + /* Dictionary combo */ + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), + FALSE, TRUE, 0 ); + GtkWidget* dictCombo = gtk_combo_box_text_new(); + g_signal_connect( dictCombo, "changed", + G_CALLBACK(dict_combo_changed), state ); + gtk_widget_show( dictCombo ); + gtk_box_pack_start( GTK_BOX(hbox), dictCombo, FALSE, TRUE, 0 ); + + GSList* dicts = listDicts( state->globals->cGlobals.params ); + GSList* iter = dicts; + for ( int ii = 0; !!iter; iter = iter->next, ++ii ) { + const gchar* name = iter->data; + gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(dictCombo), name ); + if ( !!state->gi->dictName ) { + if ( !strcmp( name, state->gi->dictName ) ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); + } + } else if ( 0 == ii ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); + } + } + g_slist_free( dicts ); + + addPhoniesCombo( state, hbox ); + + gtk_box_pack_start( GTK_BOX(parent), hbox, FALSE, TRUE, 0 ); +} /* addDictsRow */ + static GtkWidget* makeNewGameDialog( GtkNewGameState* state ) { @@ -288,11 +411,6 @@ makeNewGameDialog( GtkNewGameState* state ) GtkWidget* roleCombo; char* roles[] = { "Standalone", "Host", "Guest" }; #endif - GtkWidget* nPlayersCombo; - GtkWidget* boardSizeCombo; - GtkWidget* dictCombo; - CurGameInfo* gi; - short ii; dialog = gtk_dialog_new(); gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); @@ -306,7 +424,7 @@ makeNewGameDialog( GtkNewGameState* state ) roleCombo = gtk_combo_box_text_new(); state->roleCombo = roleCombo; - for ( ii = 0; ii < VSIZE(roles); ++ii ) { + for ( int ii = 0; ii < VSIZE(roles); ++ii ) { gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(roleCombo), roles[ii] ); } @@ -327,12 +445,10 @@ makeNewGameDialog( GtkNewGameState* state ) state->nPlayersLabel = gtk_label_new(""); gtk_box_pack_start( GTK_BOX(hbox), state->nPlayersLabel, FALSE, TRUE, 0 ); - nPlayersCombo = gtk_combo_box_text_new(); + GtkWidget* nPlayersCombo = gtk_combo_box_text_new(); state->nPlayersCombo = nPlayersCombo; - gi = state->gi; - - for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { + for ( int ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { char buf[2] = { ii + '1', '\0' }; gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(nPlayersCombo), buf ); @@ -351,7 +467,7 @@ makeNewGameDialog( GtkNewGameState* state ) gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); - for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { + for ( int ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { GtkWidget* label = gtk_label_new("Name:"); #ifndef XWFEATURE_STANDALONE_ONLY GtkWidget* remoteCheck = gtk_check_button_new_with_label( "Remote" ); @@ -401,58 +517,8 @@ makeNewGameDialog( GtkNewGameState* state ) gtk_widget_show( hbox ); } - /* board size choices */ - hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); - gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Board size"), - FALSE, TRUE, 0 ); - - boardSizeCombo = gtk_combo_box_text_new(); - if ( !state->isNewGame ) { - gtk_widget_set_sensitive( boardSizeCombo, FALSE ); - } - - for ( ii = 0; ii < MAX_SIZE_CHOICES; ++ii ) { - char buf[10]; - XP_U16 siz = MAX_COLS - ii; - snprintf( buf, sizeof(buf), "%dx%d", siz, siz ); - gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(boardSizeCombo), buf ); - if ( siz == state->nCols ) { - gtk_combo_box_set_active( GTK_COMBO_BOX(boardSizeCombo), ii ); - } - } - - g_signal_connect( boardSizeCombo, "changed", - G_CALLBACK(size_combo_changed), state ); - - gtk_widget_show( boardSizeCombo ); - gtk_box_pack_start( GTK_BOX(hbox), boardSizeCombo, FALSE, TRUE, 0 ); - - /* Dictionary combo */ - - gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), - FALSE, TRUE, 0 ); - dictCombo = gtk_combo_box_text_new(); - g_signal_connect( dictCombo, "changed", - G_CALLBACK(dict_combo_changed), state ); - gtk_widget_show( dictCombo ); - gtk_box_pack_start( GTK_BOX(hbox), dictCombo, FALSE, TRUE, 0 ); - - GSList* dicts = listDicts( state->globals->cGlobals.params ); - GSList* iter; - for ( iter = dicts, ii = 0; !!iter; iter = iter->next, ++ii ) { - const gchar* name = iter->data; - gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(dictCombo), name ); - if ( !!gi->dictName ) { - if ( !strcmp( name, gi->dictName ) ) { - gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); - } - } else if ( 0 == ii ) { - gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); - } - } - g_slist_free( dicts ); - - addPhoniesCombo( state, hbox ); + addSizesRow( state, vbox ); + addDictsRow( state, vbox ); gtk_widget_show( hbox ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); @@ -664,6 +730,18 @@ setDefaults( CurGameInfo* gi ) } } +static void +checkAndWarn( GtkNewGameState* state, GtkWidget* dialog ) +{ + if ( state->nTrayTiles < state->bingoMin ) { + gchar buf[128]; + XP_SNPRINTF( buf, VSIZE(buf),"\"%s\" cannot be greater than \"%s\"", + BINGO_THRESHOLD, TRAY_SIZE ); + gtktell( dialog, buf ); + state->revert = XP_TRUE; + } +} + gboolean gtkNewGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, XP_Bool isNewGame, XP_Bool fireConnDlg ) @@ -694,6 +772,8 @@ gtkNewGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, state.revert = FALSE; state.loaded = XP_FALSE; state.nCols = gi->boardSize; + state.nTrayTiles = gi->traySize; + state.bingoMin = gi->bingoMin; if ( 0 == state.nCols ) { state.nCols = globals->cGlobals.params->pgi.boardSize; } @@ -707,9 +787,13 @@ gtkNewGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, state.loaded = XP_TRUE; gtk_main(); + + checkAndWarn( &state, dialog ); if ( !state.cancelled && !state.revert ) { if ( newg_store( state.newGameCtxt, NULL_XWE, gi, XP_TRUE ) ) { gi->boardSize = state.nCols; + gi->traySize = state.nTrayTiles; + gi->bingoMin = state.bingoMin; replaceStringIfDifferent( globals->cGlobals.util->mpool, &gi->dictName, state.dict ); gi->phoniesAction = state.phoniesAction; diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c index 98556caea..42dfc773d 100644 --- a/xwords4/linux/lindutil.c +++ b/xwords4/linux/lindutil.c @@ -222,6 +222,8 @@ linux_dutil_getUserString( XW_DUtilCtxt* XP_UNUSED(uc), return (XP_UCHAR*)"Score for turn: %d\n"; case STR_BONUS_ALL: return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; + case STR_BONUS_ALL_SUB: + return (XP_UCHAR*)"Bonus for using at least %d tiles: 50\n"; case STR_PENDING_PLAYER: return (XP_UCHAR*)"(remote)"; case STRD_TIME_PENALTY_SUB: diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 9a8c6d145..9c175e461 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -838,6 +838,7 @@ typedef enum { ,CMD_NOCLOSESTDIN ,CMD_QUITAFTER ,CMD_BOARDSIZE + ,CMD_TRAYSIZE ,CMD_DUP_MODE ,CMD_HIDEVALUES ,CMD_SKIPCONFIRM @@ -985,6 +986,7 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_NOCLOSESTDIN, false, "no-close-stdin", "do not close stdin on start" } ,{ CMD_QUITAFTER, true, "quit-after", "exit seconds after game's done" } ,{ CMD_BOARDSIZE, true, "board-size", "board is by cells" } + ,{ CMD_TRAYSIZE, true, "tray-size", " tiles per tray (7-9 are legal)" } ,{ CMD_DUP_MODE, false, "duplicate-mode", "play in duplicate mode" } ,{ CMD_HIDEVALUES, false, "hide-values", "show letters, not nums, on tiles" } ,{ CMD_SKIPCONFIRM, false, "skip-confirm", "don't confirm before commit" } @@ -2690,6 +2692,8 @@ main( int argc, char** argv ) mainParams.connInfo.sms.port = 1; #endif mainParams.pgi.boardSize = 15; + mainParams.pgi.traySize = 7; + mainParams.pgi.bingoMin = 7; mainParams.quitAfter = -1; mainParams.sleepOnAnchor = XP_FALSE; mainParams.printHistory = XP_FALSE; @@ -3044,6 +3048,11 @@ main( int argc, char** argv ) case CMD_BOARDSIZE: mainParams.pgi.boardSize = atoi(optarg); break; + case CMD_TRAYSIZE: + mainParams.pgi.traySize = atoi(optarg); + XP_ASSERT( MIN_TRAY_TILES <= mainParams.pgi.traySize + && mainParams.pgi.traySize <= MAX_TRAY_TILES ); + break; case CMD_DUP_MODE: mainParams.pgi.inDuplicateMode = XP_TRUE; break; diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index 88476b554..f46aedd44 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -510,6 +510,7 @@ def build_cmds(args): if DEV == 1 or usePublic: PARAMS += ['--force-game'] if DEV == 1: PARAMS += ['--server', '--phonies', phonies ] + PARAMS += ['--tray-size', random.randint(7, 9)] # randint() is *inclusive* # IFF there are any non-1 player counts, tell inviter which if sum(LOCALS) > NDEVS: PARAMS += ['--invitee-counts', ":".join(str(n) for n in LOCALS[1:])]