diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 86f7fd9bc..002486e41 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -22,7 +22,7 @@ diff --git a/xwords4/android/XWords4/jni/Android.mk b/xwords4/android/XWords4/jni/Android.mk index f667ba729..886bcdc1f 100644 --- a/xwords4/android/XWords4/jni/Android.mk +++ b/xwords4/android/XWords4/jni/Android.mk @@ -15,8 +15,9 @@ local_DEFINES += \ $(local_DEBUG) \ -DXWFEATURE_RELAY \ -DXWFEATURE_TURNCHANGENOTIFY \ - -DXWFEATURE_CROSSHAIRS \ + -DSHOW_PROGRESS \ -DKEY_SUPPORT \ + -DXWFEATURE_CROSSHAIRS \ -DPOINTER_SUPPORT \ -DSCROLL_DRAG_THRESHHOLD=1 \ -DDROP_BITMAPS \ diff --git a/xwords4/android/XWords4/jni/utilwrapper.c b/xwords4/android/XWords4/jni/utilwrapper.c index e3cb3e60c..794a1ddcd 100644 --- a/xwords4/android/XWords4/jni/utilwrapper.c +++ b/xwords4/android/XWords4/jni/utilwrapper.c @@ -197,10 +197,12 @@ and_util_yOffsetChange(XW_UtilCtxt* uc, XP_U16 maxOffset, static void and_util_turnChanged(XW_UtilCtxt* uc) { - /* don't log; this is getting called a lot */ + UTIL_CBK_HEADER( "turnChanged", "()V" ); + (*env)->CallVoidMethod( env, util->jutil, mid ); + UTIL_CBK_TAIL(); } - #endif + static void and_util_notifyGameOver( XW_UtilCtxt* uc ) { @@ -378,13 +380,17 @@ and_util_getTraySearchLimits(XW_UtilCtxt* uc, XP_U16* min, XP_U16* max ) static void and_util_engineStarting( XW_UtilCtxt* uc, XP_U16 nBlanks ) { - LOG_FUNC(); + UTIL_CBK_HEADER("engineStarting", "(I)V" ); + (*env)->CallVoidMethod( env, util->jutil, mid, nBlanks ); + UTIL_CBK_TAIL(); } static void and_util_engineStopping( XW_UtilCtxt* uc ) { - LOG_FUNC(); + UTIL_CBK_HEADER("engineStopping", "()V" ); + (*env)->CallVoidMethod( env, util->jutil, mid ); + UTIL_CBK_TAIL(); } #endif diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index d4ef61bd1..0c9205b83 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -160,6 +160,7 @@ loadCommonPrefs( JNIEnv* env, CommonPrefs* cp, jobject j_cp ) cp->skipCommitConfirm = getBool( env, j_cp, "skipCommitConfirm" ); cp->showColors = getBool( env, j_cp, "showColors" ); cp->sortNewTiles = getBool( env, j_cp, "sortNewTiles" ); + cp->allowPeek = getBool( env, j_cp, "allowPeek" ); } static XWStreamCtxt* @@ -694,6 +695,17 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1replaceTiles return result; } +JNIEXPORT jboolean JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_board_1redoReplacedTiles +( JNIEnv* env, jclass C, jint gamePtr ) +{ + jboolean result; + XWJNI_START(); + result = board_redoReplacedTiles( state->game.board ); + XWJNI_END(); + return result; +} + JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1handleUndo (JNIEnv* env, jclass C, jint gamePtr) @@ -726,7 +738,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1resetEngine JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1requestHint ( JNIEnv* env, jclass C, jint gamePtr, jboolean useLimits, - jbooleanArray workRemains ) + jboolean goBack, jbooleanArray workRemains ) { jboolean result; XWJNI_START(); @@ -735,7 +747,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1requestHint #ifdef XWFEATURE_SEARCHLIMIT useLimits, #endif - &tmpbool ); + goBack, &tmpbool ); /* If passed need to do workRemains[0] = tmpbool */ if ( workRemains ) { jboolean jbool = tmpbool; @@ -1024,6 +1036,51 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1focusChanged } #endif +JNIEXPORT jint JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_board_1visTileCount +( JNIEnv* env, jclass C, jint gamePtr ) +{ + jint result; + XWJNI_START(); + result = board_visTileCount( state->game.board ); + XWJNI_END(); + return result; +} + +JNIEXPORT jboolean JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_board_1canHint +( JNIEnv* env, jclass C, jint gamePtr ) +{ + jboolean result; + XWJNI_START(); + result = board_canHint( state->game.board ); + XWJNI_END(); + return result; +} + +JNIEXPORT jboolean JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_board_1canShuffle +( JNIEnv* env, jclass C, jint gamePtr ) +{ + jboolean result; + XWJNI_START(); + result = board_canShuffle( state->game.board ); + XWJNI_END(); + return result; +} + +JNIEXPORT jboolean JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_board_1canTogglePending +( JNIEnv* env, jclass C, jint gamePtr ) +{ + jboolean result; + XWJNI_START(); + result = board_canTogglePending( state->game.board ); + XWJNI_END(); + return result; +} + +#ifdef KEYBOARD_NAV JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1handleKey ( JNIEnv* env, jclass C, jint gamePtr, jobject jkey, jboolean jup, @@ -1044,6 +1101,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_board_1handleKey XWJNI_END(); return result; } +#endif JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1hasComms diff --git a/xwords4/android/XWords4/res/drawable/flip.png b/xwords4/android/XWords4/res/drawable/flip.png new file mode 100644 index 000000000..6baa2f3f9 Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/flip.png differ diff --git a/xwords4/android/XWords4/res/drawable/next_hint.png b/xwords4/android/XWords4/res/drawable/next_hint.png new file mode 100644 index 000000000..e92d35561 Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/next_hint.png differ diff --git a/xwords4/android/XWords4/res/drawable/prev_hint.png b/xwords4/android/XWords4/res/drawable/prev_hint.png new file mode 100644 index 000000000..7c9ad90c3 Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/prev_hint.png differ diff --git a/xwords4/android/XWords4/res/drawable/shuffle.png b/xwords4/android/XWords4/res/drawable/shuffle.png new file mode 100644 index 000000000..498e5fa1f Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/shuffle.png differ diff --git a/xwords4/android/XWords4/res/drawable/undo.png b/xwords4/android/XWords4/res/drawable/undo.png new file mode 100644 index 000000000..052ed0af9 Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/undo.png differ diff --git a/xwords4/android/XWords4/res/drawable/values.png b/xwords4/android/XWords4/res/drawable/values.png new file mode 100644 index 000000000..b9ccf7745 Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/values.png differ diff --git a/xwords4/android/XWords4/res/drawable/zoom.png b/xwords4/android/XWords4/res/drawable/zoom.png new file mode 100644 index 000000000..c4590145b Binary files /dev/null and b/xwords4/android/XWords4/res/drawable/zoom.png differ diff --git a/xwords4/android/XWords4/res/layout/about_dlg.xml b/xwords4/android/XWords4/res/layout/about_dlg.xml index 664f383e2..623ef8908 100644 --- a/xwords4/android/XWords4/res/layout/about_dlg.xml +++ b/xwords4/android/XWords4/res/layout/about_dlg.xml @@ -6,38 +6,24 @@ > - - + + diff --git a/xwords4/android/XWords4/res/layout/board.xml b/xwords4/android/XWords4/res/layout/board.xml index dd5c9fb60..e27de5266 100644 --- a/xwords4/android/XWords4/res/layout/board.xml +++ b/xwords4/android/XWords4/res/layout/board.xml @@ -1,7 +1,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xwords4/android/XWords4/res/layout/list_item.xml b/xwords4/android/XWords4/res/layout/list_item.xml index d9b9ae638..dd56d36c4 100644 --- a/xwords4/android/XWords4/res/layout/list_item.xml +++ b/xwords4/android/XWords4/res/layout/list_item.xml @@ -11,4 +11,20 @@ android:textAppearance="?android:attr/textAppearanceLarge" android:longClickable="true" android:background="@android:drawable/list_selector_background" - /> + > + + + + + + diff --git a/xwords4/android/XWords4/res/menu/board_menu.xml b/xwords4/android/XWords4/res/menu/board_menu.xml index 21bd13b92..757783d4e 100644 --- a/xwords4/android/XWords4/res/menu/board_menu.xml +++ b/xwords4/android/XWords4/res/menu/board_menu.xml @@ -1,53 +1,37 @@ - - - - - - - - - - - - - - - - + + + + - + + + + + + + + + @@ -64,6 +48,19 @@ android:title="@string/board_menu_game_resend" /> + + + + + + + + - diff --git a/xwords4/android/XWords4/res/menu/players_list_item_menu.xml b/xwords4/android/XWords4/res/menu/players_list_item_menu.xml deleted file mode 100644 index c99cdad77..000000000 --- a/xwords4/android/XWords4/res/menu/players_list_item_menu.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/xwords4/android/XWords4/res/values-ca/strings.xml b/xwords4/android/XWords4/res/values-ca/strings.xml index d2532cdcb..8d69d152f 100644 --- a/xwords4/android/XWords4/res/values-ca/strings.xml +++ b/xwords4/android/XWords4/res/values-ca/strings.xml @@ -19,13 +19,14 @@ Afegeix una partida Afegeix una partida + Vés a la partida Configura Amaga Suprimeix Copia Reinicialitza - Nova des de + Nova Mou amunt Mou avall Mou amunt de tot @@ -185,8 +186,18 @@ Mostra el resum de la jugada dels jugadors robot Omet la confirmació de la jugada No mostra el resum de la jugada dels jugadors humans + Ordena les fitxes noves + Ordena el faristol en agafar noves fitxes Zoom amb els botons de volum Apropa o allunya el tauler fent servir les tecles de volum. + Clic per a jugar + obre la partida en fer-hi clic al llistat de partides, en compte d\'obrir un menú d\'opcions + + Amaga la capçalera + Permet que el tauler sigui una mica més gran + + Mostra les bonificacions + Mostra el text com "2P" a les caselles bonificadores lliures Solitari Amfitrió @@ -215,7 +226,13 @@ Valors predeterminats per a partides noves Paràmetres predeterminats per a partides noves - Diccionari de la partida + + Aspecte + Paràmetres que controlen l\'aspecte + Comportament + Paràmetres que controlen el comportament de l\'aplicació + + Diccionari arbitral Paraules fora del dicc. Mida del tauler Usa el rellotge @@ -232,9 +249,15 @@ Triple de lletra Doble de paraula Triple de paraula + 2L + 2P + 3L + 3P + pts Canvi de fitxes. Pitgeu\n\'Fet\' quan hagueu acabat. Fons de la fitxa Cel·la buida/fons + Creu d\'ubicació Color del focus Avançat No hauríeu de necessitar canviar això diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 17daacbef..d680ea48d 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -9,6 +9,7 @@ key_explain_robot key_skip_confirm key_sort_tiles + key_peek_other key_hide_values key_ringer_zoom key_click_launches diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 2085077f0..a88b942fb 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -83,7 +83,7 @@ Undo last Hint Next hint - Hint + First hint Show values Game Counts and values @@ -135,7 +135,7 @@ Too few tiles left to exchange. Tile assignment can't be undone. The hint feature is disabled for this game. Enable it for a new game using the Settings dialog. - No peeking at remote players' tiles! + No peeking at remote players\' tiles! Refused attempt to register unexpected user[s] Conflict between Host and Guest dictionaries; Host wins. At least one player must be marked \"Remote\" for a game started as Host. @@ -180,6 +180,7 @@ delete all games? This action cannot be undone. Allow hints + Searching for moves Enable game timer Color tiles Draw tiles using color of @@ -197,16 +198,16 @@ Sort trays whenever new tiles are added Volume keys zoom - Zoom board using volume keys - rather than on-screen buttons + Zoom board using volume keys Tap to play Tapping on game in games list opens it rather than dropping a menu - Hide titlebar Hiding the game name lets the board be slightly larger - + View tiles out-of-turn + Tapping on scoreboard name shows + that player\'s tiles Show bonus values Include text like "2W" on empty bonus squares @@ -339,6 +340,9 @@ + Toolbar icons by Sarah Chu; other + credits pending permission. + Downloading Crosswords dictionary %s... Dictionary not found diff --git a/xwords4/android/XWords4/res/values/styles.xml b/xwords4/android/XWords4/res/values/styles.xml index 78b2ebef7..c51814546 100644 --- a/xwords4/android/XWords4/res/values/styles.xml +++ b/xwords4/android/XWords4/res/values/styles.xml @@ -37,5 +37,20 @@ 3 + + + + diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 2804a950f..81346e76d 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -152,6 +152,11 @@ android:summary="@string/click_launches_summary" android:defaultValue="false" /> + 0 ) { + m_handler.post( new Runnable() { + public void run() { + String title = getString( R.string.progress_title ); + m_progress = ProgressDialog.show( BoardActivity.this, + title, null, true, + true ); + } + } ); + } + } + + public void engineStopping() + { + Utils.logf( "engineStopping" ); + m_handler.post( new Runnable() { + public void run() { + if ( null != m_progress ) { + m_progress.cancel(); + m_progress = null; + } + } + } ); + } + public String getUserString( int stringCode ) { int id = 0; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java index 5d743ba45..a088c1651 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java @@ -33,9 +33,7 @@ import android.view.MotionEvent; import android.graphics.drawable.Drawable; import android.content.res.Resources; import android.graphics.Paint.FontMetricsInt; -import android.widget.ZoomButtonsController; import android.os.Handler; -import java.util.HashMap; import java.nio.IntBuffer; import junit.framework.Assert; @@ -117,9 +115,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler, private int[] m_playerColors; private int[] m_otherColors; private String[] m_bonusSummaries; - private ZoomButtonsController m_zoomButtons; - private boolean m_useZoomControl; - private boolean m_canZoom; // called when inflating xml public BoardView( Context context, AttributeSet attrs ) @@ -132,15 +127,13 @@ public class BoardView extends View implements DrawCtx, BoardHandler, { int action = event.getAction(); int xx = (int)event.getX() - m_left; - int yy = (int)event.getY() - getCurTop(); + int yy = (int)event.getY() - m_top; switch ( action ) { case MotionEvent.ACTION_DOWN: - enableZoomControlsIf(); m_jniThread.handle( JNIThread.JNICmd.CMD_PEN_DOWN, xx, yy ); break; case MotionEvent.ACTION_MOVE: - enableZoomControlsIf(); m_jniThread.handle( JNIThread.JNICmd.CMD_PEN_MOVE, xx, yy ); break; case MotionEvent.ACTION_UP: @@ -160,18 +153,11 @@ public class BoardView extends View implements DrawCtx, BoardHandler, { synchronized( this ) { if ( layoutBoardOnce() ) { - canvas.drawBitmap( m_bitmap, m_left, getCurTop(), m_drawPaint ); + canvas.drawBitmap( m_bitmap, m_left, m_top, m_drawPaint ); } } } - @Override - protected void onDetachedFromWindow() - { - m_zoomButtons.setVisible( false ); - super.onDetachedFromWindow(); - } - private void init( Context context ) { m_context = context; @@ -215,33 +201,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler, } m_viewHandler = new Handler(); - m_zoomButtons = new ZoomButtonsController( this ); - ZoomButtonsController.OnZoomListener lstnr = - new ZoomButtonsController.OnZoomListener(){ - public void onVisibilityChanged( boolean visible ){} - public void onZoom( boolean zoomIn ) - { - if ( null != m_jniThread ) { - int zoomBy = zoomIn ? 1 : -1; - m_jniThread.handle( JNIThread.JNICmd.CMD_ZOOM, zoomBy ); - } - } - }; - m_zoomButtons.setOnZoomListener( lstnr ); - m_zoomButtons.setZoomSpeed( 100 ); // milliseconds - } - - protected void setUseZoomControl( boolean useZoomControl ) - { - m_useZoomControl = useZoomControl; - if ( !useZoomControl ) { - m_zoomButtons.setVisible( false ); - } - } - - private int getCurTop() - { - return m_useZoomControl ? 0 : m_top; } private BoardDims figureBoardDims( int width, int height, @@ -309,9 +268,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler, m_letterRect = null; m_valRect = null; - // We hide zoom on change in orientation - m_zoomButtons.setVisible( false ); - BoardDims dims = figureBoardDims( width, height, m_gi ); m_left = dims.left; m_top = dims.top; @@ -329,15 +285,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler, return layoutDone; } // layoutBoardOnce - private void enableZoomControlsIf() - { - if ( m_useZoomControl && m_canZoom ) { - if ( m_layoutWidth <= m_layoutHeight ) { - m_zoomButtons.setVisible( true ); - } - } - } - // BoardHandler interface implementation public void startHandling( JNIThread thread, int gamePtr, CurGameInfo gi ) { @@ -375,17 +322,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler, } } - public void zoomChanged( final boolean[] canZoom ) - { - m_viewHandler.post( new Runnable() { - public void run() { - m_zoomButtons.setZoomInEnabled( canZoom[0] ); - m_zoomButtons.setZoomOutEnabled( canZoom[1] ); - m_canZoom = canZoom[0] || canZoom[1]; - } - } ); - } - // DrawCtxt interface implementation public boolean scoreBegin( Rect rect, int numPlayers, int[] scores, int remCount, int dfs ) @@ -550,7 +486,9 @@ public class BoardView extends View implements DrawCtx, BoardHandler, m_origin.draw( m_canvas ); } else if ( null != bonusStr ) { m_fillPaint.setColor( GREY ); - drawCentered( bonusStr, rect, m_fontDims ); + Rect brect = new Rect( rect ); + brect.inset( 0, (brect.height() - m_defaultFontHt)/2 ); + drawCentered( bonusStr, brect, m_fontDims ); } } else { m_fillPaint.setColor( foreColor ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java index a1aae15ee..72c2388f2 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictsActivity.java @@ -39,7 +39,8 @@ import android.content.SharedPreferences; import junit.framework.Assert; public class DictsActivity extends ListActivity - implements View.OnClickListener { + implements View.OnClickListener, + XWListItem.DeleteCallback { String[] m_dicts; private class DictListAdapter extends XWListAdapter { @@ -58,6 +59,12 @@ public class DictsActivity extends ListActivity = (XWListItem)factory.inflate( R.layout.list_item, null ); view.setPosition( position ); view.setText( m_dicts[position] ); + + if ( !GameUtils.dictIsBuiltin( DictsActivity.this, + m_dicts[position] ) ) { + view.setDeleteCallback( DictsActivity.this ); + } + return view; } } @@ -88,8 +95,8 @@ public class DictsActivity extends ListActivity @Override public void onCreateContextMenu( ContextMenu menu, View view, - ContextMenuInfo menuInfo ) { - + ContextMenuInfo menuInfo ) + { super.onCreateContextMenu( menu, view, menuInfo ); MenuInflater inflater = getMenuInflater(); @@ -97,12 +104,6 @@ public class DictsActivity extends ListActivity AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; - - String dict = m_dicts[info.position]; - if ( GameUtils.dictIsBuiltin( this, dict ) ) { - MenuItem item = menu.findItem( R.id.dicts_item_delete ); - item.setVisible( false ); - } } @Override @@ -127,11 +128,6 @@ public class DictsActivity extends ListActivity editor.putString( key, m_dicts[info.position] ); editor.commit(); break; - case R.id.dicts_item_delete: - GameUtils.deleteDict( this, m_dicts[info.position] ); - mkListAdapter(); - handled = true; - break; case R.id.dicts_item_details: Utils.notImpl( this ); break; @@ -140,6 +136,13 @@ public class DictsActivity extends ListActivity return handled; } + // DeleteCallback interface + public void deleteCalled( int myPosition ) + { + GameUtils.deleteDict( this, m_dicts[myPosition] ); + mkListAdapter(); + } + private void mkListAdapter() { m_dicts = GameUtils.dictList( this ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java index a7322a01d..470f20691 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameConfig.java @@ -41,8 +41,6 @@ import android.content.DialogInterface; import android.view.LayoutInflater; import android.widget.CheckBox; import android.widget.CompoundButton; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuInflater; import android.view.KeyEvent; import android.widget.Spinner; @@ -56,7 +54,8 @@ import junit.framework.Assert; import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; -public class GameConfig extends Activity implements View.OnClickListener { +public class GameConfig extends Activity implements View.OnClickListener, + XWListItem.DeleteCallback { private static final int PLAYER_EDIT = 1; private static final int ROLE_EDIT_RELAY = 2; @@ -453,42 +452,12 @@ public class GameConfig extends Activity implements View.OnClickListener { setTitle( String.format( fmt, GameUtils.gameName( this, m_path ) ) ); } // onCreate - @Override - public void onCreateContextMenu( ContextMenu menu, View view, - ContextMenuInfo menuInfo ) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate( R.menu.players_list_item_menu, menu ); - m_whichPlayer = ((XWListItem)view).getPosition(); - } - - @Override - public boolean onContextItemSelected( MenuItem item ) + // DeleteCallback interface + public void deleteCalled( int myPosition ) { - boolean handled = true; - boolean changed = false; - - switch (item.getItemId()) { - // case R.id.player_list_item_edit: - // showDialog( PLAYER_EDIT ); - // break; - case R.id.player_list_item_delete: - changed = m_gi.delete( m_whichPlayer ); - break; - case R.id.player_list_item_up: - changed = m_gi.moveUp( m_whichPlayer ); - break; - case R.id.player_list_item_down: - changed = m_gi.moveDown( m_whichPlayer ); - break; - default: - handled = false; - } - - if ( changed ) { + if ( m_gi.delete( myPosition ) ) { loadPlayers(); } - - return handled; } public void onClick( View view ) @@ -560,8 +529,11 @@ public class GameConfig extends Activity implements View.OnClickListener { view.setPosition( ii ); view.setText( names[ii] ); view.setGravity( Gravity.CENTER ); + // only enable delete if one will remain + if ( 1 < names.length ) { + view.setDeleteCallback( this ); + } - registerForContextMenu( view ); view.setOnClickListener( new View.OnClickListener() { @Override public void onClick( View view ) { @@ -576,6 +548,13 @@ public class GameConfig extends Activity implements View.OnClickListener { m_playerLayout.addView( divider ); } + m_addPlayerButton + .setVisibility( names.length >= CurGameInfo.MAX_NUM_PLAYERS? + View.GONE : View.VISIBLE ); + m_jugglePlayersButton + .setVisibility( names.length <= 1 ? + View.GONE : View.VISIBLE ); + if ( DeviceRole.SERVER_ISSERVER == m_gi.serverRole && 0 == m_gi.remoteCount() ) { showDialog( FORCE_REMOTE ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Toolbar.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Toolbar.java new file mode 100644 index 000000000..7ea5bf965 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Toolbar.java @@ -0,0 +1,138 @@ +/* -*- compile-command: "cd ../../../../../; ant install"; -*- */ +/* + * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All + * rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +package org.eehouse.android.xw4; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ImageButton; +//import android.view.LayoutInflater; +//import java.util.HashMap; +//import junit.framework.Assert; + +import org.eehouse.android.xw4.jni.*; + +public class Toolbar { + + private static class TBButtonInfo { + public TBButtonInfo( int... ids ) { + m_ids = ids; + } + public int m_ids[]; + } + + public static final int BUTTON_HINT_PREV = 0; + public static final int BUTTON_HINT_NEXT = 1; + public static final int BUTTON_FLIP = 2; + public static final int BUTTON_JUGGLE = 3; + public static final int BUTTON_ZOOM = 4; + public static final int BUTTON_UNDO = 5; + public static final int BUTTON_VALUES = 6; + + private static TBButtonInfo[] s_buttonInfo = { + // BUTTON_HINT_PREV + new TBButtonInfo(R.id.prevhint_button_horizontal, + R.id.prevhint_button_vertical), + // BUTTON_HINT_NEXT + new TBButtonInfo(R.id.nexthint_button_horizontal, + R.id.nexthint_button_vertical), + // BUTTON_FLIP + new TBButtonInfo(R.id.flip_button_horizontal, + R.id.flip_button_vertical), + // BUTTON_JUGGLE + new TBButtonInfo( R.id.shuffle_button_horizontal, + R.id.shuffle_button_vertical ), + // BUTTON_ZOOM + new TBButtonInfo( R.id.zoom_button_horizontal, + R.id.zoom_button_vertical ), + // BUTTON_UNDO + new TBButtonInfo( R.id.undo_button_horizontal, + R.id.undo_button_vertical ), + // BUTTON_VALUES + new TBButtonInfo( R.id.values_button_horizontal, + R.id.values_button_vertical ), + }; + + private Activity m_activity; + private LinearLayout m_horLayout; + private LinearLayout m_vertLayout; + + private enum ORIENTATION { ORIENT_UNKNOWN, + ORIENT_PORTRAIT, + ORIENT_LANDSCAPE, + }; + private ORIENTATION m_curOrient = ORIENTATION.ORIENT_UNKNOWN; + + public Toolbar( Activity activity, View horLayout, View vertLayout ) + { + m_activity = activity; + m_horLayout = (LinearLayout)horLayout; + m_vertLayout = (LinearLayout)vertLayout; + } + + public void setListener( int index, View.OnClickListener listener ) + { + TBButtonInfo info = s_buttonInfo[index]; + for ( int id : info.m_ids ) { + ImageButton button = (ImageButton)m_activity.findViewById( id ); + button.setOnClickListener( listener ); + } + } + + public void orientChanged( boolean landscape ) + { + if ( landscape && m_curOrient == ORIENTATION.ORIENT_LANDSCAPE ) { + // do nothing + } else if ( !landscape && m_curOrient == ORIENTATION.ORIENT_PORTRAIT ) { + // do nothing + } else { + LinearLayout prevLayout, nextLayout; + if ( landscape ) { + m_curOrient = ORIENTATION.ORIENT_LANDSCAPE; + prevLayout = m_horLayout; + nextLayout = m_vertLayout; + } else { + m_curOrient = ORIENTATION.ORIENT_PORTRAIT; + prevLayout = m_vertLayout; + nextLayout = m_horLayout; + } + + prevLayout.setVisibility( View.GONE ); + nextLayout.setVisibility( View.VISIBLE ); + } + } + + public void update( int index, int enable ) + { + boolean show = enable!=0; + TBButtonInfo info = s_buttonInfo[index]; + int vis = enable != 0 ? View.VISIBLE : View.GONE; + + ImageButton button; + for ( int id : info.m_ids ) { + button = (ImageButton)m_activity.findViewById( id ); + button.setVisibility( vis ); + } + } + +} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java index 5be7ef1e2..6417ecb2c 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWConstants.java @@ -23,5 +23,5 @@ package org.eehouse.android.xw4; public interface XWConstants { public static final String GAME_EXTN = ".xwg"; public static final String DICT_EXTN = ".xwd"; - public static final String VERSION_STR = "4.4 beta 12"; + public static final String VERSION_STR = "4.4 beta 13"; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListItem.java index 813a77b76..64bc15331 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListItem.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListItem.java @@ -20,18 +20,48 @@ package org.eehouse.android.xw4; +import android.widget.LinearLayout; +import android.view.View; import android.widget.TextView; +import android.widget.ImageButton; import android.content.Context; import android.util.AttributeSet; import android.graphics.Rect; -public class XWListItem extends TextView { +public class XWListItem extends LinearLayout { private int m_position; + private ImageButton m_button; + private Context m_context; + DeleteCallback m_cb; + + public interface DeleteCallback { + void deleteCalled( int myPosition ); + } public XWListItem( Context cx, AttributeSet as ) { super( cx, as ); + m_context = cx; } public int getPosition() { return m_position; } public void setPosition( int indx ) { m_position = indx; } + + public void setText( String text ) + { + TextView view = (TextView)getChildAt( 0 ); + view.setText( text ); + } + + public void setDeleteCallback( DeleteCallback cb ) + { + m_cb = cb; + ImageButton button = (ImageButton)getChildAt( 1 ); + button.setOnClickListener( new View.OnClickListener() { + @Override + public void onClick( View view ) { + m_cb.deleteCalled( m_position ); + } + } ); + button.setVisibility( View.VISIBLE ); + } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java index 11678d462..e05a4ba3b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/CommonPrefs.java @@ -45,6 +45,7 @@ public class CommonPrefs { public boolean skipCommitConfirm; public boolean showColors; public boolean sortNewTiles; + public boolean allowPeek; public int[] playerColors; public int[] bonusColors; @@ -78,6 +79,7 @@ public class CommonPrefs { R.string.key_skip_confirm, false ); showColors = getBoolean( context, sp, R.string.key_color_tiles, true ); sortNewTiles = getBoolean( context, sp, R.string.key_sort_tiles, true ); + allowPeek = getBoolean( context, sp, R.string.key_peek_other, false ); int ids[] = { R.string.key_player0, R.string.key_player1, diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index 0e02bf1f4..9eaf3a5e3 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -34,6 +34,7 @@ import org.eehouse.android.xw4.R; import org.eehouse.android.xw4.BoardDims; import org.eehouse.android.xw4.GameUtils; import org.eehouse.android.xw4.DBUtils; +import org.eehouse.android.xw4.Toolbar; import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole; public class JNIThread extends Thread { @@ -63,6 +64,8 @@ public class JNIThread extends Thread { CMD_UNDO_LAST, CMD_HINT, CMD_ZOOM, + CMD_TOGGLEZOOM, + CMD_PREV_HINT, CMD_NEXT_HINT, CMD_VALUES, CMD_COUNTS_VALUES, @@ -79,6 +82,7 @@ public class JNIThread extends Thread { public static final int DRAW = 2; public static final int DIALOG = 3; public static final int QUERY_ENDGAME = 4; + public static final int TOOLBAR_STATES = 5; private boolean m_stopped = false; private int m_jniGamePtr; @@ -196,6 +200,31 @@ public class JNIThread extends Thread { { boolean draw = false; return draw; + } // processKeyEvent + + private void checkButtons() + { + int visTileCount = XwJNI.board_visTileCount( m_jniGamePtr ); + int canFlip = visTileCount > 1 ? 1 : 0; + Message.obtain( m_handler, TOOLBAR_STATES, Toolbar.BUTTON_FLIP, + canFlip ).sendToTarget(); + int canValues = visTileCount > 0 ? 1 : 0; + Message.obtain( m_handler, TOOLBAR_STATES, Toolbar.BUTTON_VALUES, + canValues ).sendToTarget(); + + int canShuffle = XwJNI.board_canShuffle( m_jniGamePtr ) ? 1 : 0; + Message.obtain( m_handler, TOOLBAR_STATES, Toolbar.BUTTON_JUGGLE, + canShuffle ).sendToTarget(); + + int canRedo = XwJNI.board_canTogglePending( m_jniGamePtr ) ? 1 : 0; + Message.obtain( m_handler, TOOLBAR_STATES, Toolbar.BUTTON_UNDO, + canRedo ).sendToTarget(); + + int canHint = XwJNI.board_canHint( m_jniGamePtr ) ? 1 : 0; + Message.obtain( m_handler, TOOLBAR_STATES, Toolbar.BUTTON_HINT_PREV, + canHint ).sendToTarget(); + Message.obtain( m_handler, TOOLBAR_STATES, Toolbar.BUTTON_HINT_NEXT, + canHint ).sendToTarget(); } public void run() @@ -321,7 +350,8 @@ public class JNIThread extends Thread { draw = XwJNI.board_beginTrade( m_jniGamePtr ); break; case CMD_UNDO_CUR: - draw = XwJNI.board_replaceTiles( m_jniGamePtr ); + draw = XwJNI.board_replaceTiles( m_jniGamePtr ) + || XwJNI.board_redoReplacedTiles( m_jniGamePtr ); break; case CMD_UNDO_LAST: XwJNI.server_handleUndo( m_jniGamePtr ); @@ -330,19 +360,37 @@ public class JNIThread extends Thread { case CMD_HINT: XwJNI.board_resetEngine( m_jniGamePtr ); - // fallthru + handle( JNICmd.CMD_NEXT_HINT ); + break; + case CMD_NEXT_HINT: - draw = XwJNI.board_requestHint( m_jniGamePtr, false, barr ); + case CMD_PREV_HINT: + if ( nextSame( elem.m_cmd ) ) { + continue; + } + draw = XwJNI.board_requestHint( m_jniGamePtr, false, + JNICmd.CMD_PREV_HINT==elem.m_cmd, + barr ); if ( barr[0] ) { - handle( JNICmd.CMD_NEXT_HINT ); + handle( elem.m_cmd ); + draw = false; } break; + case CMD_TOGGLEZOOM: + XwJNI.board_zoom( m_jniGamePtr, 0 , barr ); + int zoomBy = 0; + if ( barr[1] ) { // always go out if possible + zoomBy = -4; + } else if ( barr[0] ) { + zoomBy = 4; + } + draw = XwJNI.board_zoom( m_jniGamePtr, zoomBy, barr ); + break; case CMD_ZOOM: draw = XwJNI.board_zoom( m_jniGamePtr, ((Integer)args[0]).intValue(), barr ); - m_drawer.zoomChanged( barr ); break; case CMD_VALUES: @@ -433,6 +481,8 @@ public class JNIThread extends Thread { // main UI thread has to invalidate view as it created // it. Message.obtain( m_handler, DRAW ).sendToTarget(); + + checkButtons(); } } Utils.logf( "run exiting" ); @@ -444,4 +494,5 @@ public class JNIThread extends Thread { // Utils.logf( "adding: " + cmd.toString() ); m_queue.add( elem ); } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/SyncedDraw.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/SyncedDraw.java index 3048b255a..856a423b9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/SyncedDraw.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/SyncedDraw.java @@ -25,5 +25,4 @@ import android.graphics.Rect; public interface SyncedDraw { void doJNIDraw(); void doIconDraw( int resID, final Rect rect ); - void zoomChanged( boolean[] canZoom ); } 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 d0f4cc02f..339422c39 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 @@ -32,8 +32,11 @@ public interface UtilCtxt { int playerNum, String[] texts ); String askPassword( String name ); + void turnChanged(); boolean engineProgressCallback(); + void engineStarting( int nBlanks ); + void engineStopping(); // Values for why; should be enums public static final int TIMER_PENDOWN = 1; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java index cedce1ee1..bc07980db 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java @@ -137,14 +137,21 @@ public class XwJNI { public static native boolean board_commitTurn( int gamePtr ); public static native boolean board_flip( int gamePtr ); public static native boolean board_replaceTiles( int gamePtr ); + public static native boolean board_redoReplacedTiles( int gamePtr ); public static native void board_resetEngine( int gamePtr ); public static native boolean board_requestHint( int gamePtr, boolean useTileLimits, + boolean goBackwards, boolean[] workRemains ); public static native boolean board_beginTrade( int gamePtr ); public static native String board_formatRemainingTiles( int gamePtr ); + public static native int board_visTileCount( int gamePtr ); + public static native boolean board_canHint( int gamePtr ); + public static native boolean board_canShuffle( int gamePtr ); + public static native boolean board_canTogglePending( int gamePtr ); + public enum XP_Key { XP_KEY_NONE, XP_CURSOR_KEY_DOWN, diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 5d12de3f2..d809f9295 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -72,8 +72,6 @@ # define MAX_BOARD_ZOOM 4 #endif -#define BOARD_ZOOMBY 2 - #ifdef CPLUS extern "C" { #endif @@ -424,6 +422,7 @@ board_prefsChanged( BoardCtxt* board, CommonPrefs* cp ) board->hideValsInTray = cp->hideTileValues; board->skipCommitConfirm = cp->skipCommitConfirm; board->showColors = cp->showColors; + board->allowPeek = cp->allowPeek; if ( showArrowChanged ) { showArrowChanged = setArrowVisible( board, XP_FALSE ); @@ -537,6 +536,38 @@ board_getYOffset( const BoardCtxt* board ) return vsd->offset; } /* board_getYOffset */ +XP_U16 +board_visTileCount( const BoardCtxt* board ) +{ + return model_visTileCount( board->model, board->selPlayer, + TRAY_REVEALED == board->trayVisState ); +} + +XP_Bool +board_canShuffle( const BoardCtxt* board ) +{ + return model_canShuffle( board->model, board->selPlayer, + TRAY_REVEALED == board->trayVisState ); +} + +XP_Bool +board_canTogglePending( const BoardCtxt* board ) +{ + return TRAY_REVEALED == board->trayVisState + && model_canTogglePending( board->model, board->selPlayer ); +} + +XP_Bool +board_canHint( const BoardCtxt* board ) +{ + XP_Bool canHint = !board->gi->hintsNotAllowed; + if ( canHint ) { + LocalPlayer* lp = &board->gi->players[board->selPlayer]; + canHint = lp->isLocal && !lp->isRobot; + } + return canHint; +} + static XP_U16 adjustOffset( XP_U16 curOffset, XP_S16 zoomBy ) { @@ -561,20 +592,13 @@ canZoomIn( const BoardCtxt* board, XP_S16 newCount ) } XP_Bool -board_zoom( BoardCtxt* board, XP_S16 zoomDir, XP_Bool* canInOut ) +board_zoom( BoardCtxt* board, XP_S16 zoomBy, XP_Bool* canInOut ) { - XP_S16 zoomBy = 0; XP_Bool changed; XP_S16 zoomCount = board->zoomCount; ScrollData* hsd = &board->sd[SCROLL_H]; ScrollData* vsd = &board->sd[SCROLL_V]; - if ( zoomDir > 0 ) { - zoomBy = BOARD_ZOOMBY; - } else if ( zoomDir < 0 ) { - zoomBy = -BOARD_ZOOMBY; - } - XP_U16 maxCount = model_numCols( board->model ) - MAX_BOARD_ZOOM; if ( board->boardBounds.width > board->boardBounds.height ) { XP_U16 ratio = board->boardBounds.width / board->boardBounds.height; @@ -592,8 +616,11 @@ board_zoom( BoardCtxt* board, XP_S16 zoomDir, XP_Bool* canInOut ) /* If we're zooming in, make sure we'll stay inside the limit */ if ( changed && zoomBy > 0 ) { - changed = canZoomIn( board, zoomCount ); + while ( zoomCount > 0 && !canZoomIn( board, zoomCount ) ) { + --zoomCount; + } } + changed = zoomCount != board->zoomCount; if ( changed ) { /* Try to distribute the zoom */ @@ -788,15 +815,17 @@ board_commitTurn( BoardCtxt* board ) * singletons that may have to be hidden or shown. */ static void -selectPlayerImpl( BoardCtxt* board, XP_U16 newPlayer, XP_Bool reveal ) +selectPlayerImpl( BoardCtxt* board, XP_U16 newPlayer, XP_Bool reveal, + XP_Bool canPeek ) { - if ( !board->gameOver && server_getCurrentTurn(board->server) < 0 ) { + XP_S16 curTurn = server_getCurrentTurn(board->server); + if ( !board->gameOver && curTurn < 0 ) { /* game not started yet; do nothing */ } else if ( board->selPlayer == newPlayer ) { if ( reveal ) { checkRevealTray( board ); } - } else { + } else if ( canPeek || newPlayer == curTurn ) { PerTurnInfo* newInfo = &board->pti[newPlayer]; XP_U16 oldPlayer = board->selPlayer; model_foreachPendingCell( board->model, newPlayer, @@ -846,9 +875,9 @@ selectPlayerImpl( BoardCtxt* board, XP_U16 newPlayer, XP_Bool reveal ) } /* selectPlayerImpl */ void -board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer ) +board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer, XP_Bool canSwitch ) { - selectPlayerImpl( board, newPlayer, XP_TRUE ); + selectPlayerImpl( board, newPlayer, XP_TRUE, canSwitch ); } /* board_selectPlayer */ void @@ -1577,6 +1606,12 @@ board_replaceTiles( BoardCtxt* board ) return result; } /* board_replaceTiles */ +XP_Bool +board_redoReplacedTiles( BoardCtxt* board ) +{ + return model_redoPendingTiles( board->model, board->selPlayer ); +} + /* There are a few conditions that must be true for any of several actions to be allowed. Check them here. */ static XP_Bool @@ -1597,7 +1632,7 @@ board_requestHint( BoardCtxt* board, #ifdef XWFEATURE_SEARCHLIMIT XP_Bool useTileLimits, #endif - XP_Bool* workRemainsP ) + XP_Bool usePrev, XP_Bool* workRemainsP ) { XP_Bool result = XP_FALSE; XP_Bool redraw = XP_FALSE; @@ -1669,7 +1704,7 @@ board_requestHint( BoardCtxt* board, #endif searchComplete = engine_findMove(engine, model, model_getDictionary(model), - tiles, nTiles, + tiles, nTiles, usePrev, #ifdef XWFEATURE_SEARCHLIMIT lp, useTileLimits, #endif @@ -1730,8 +1765,8 @@ figureHScale( BoardCtxt* board ) ScrollData* hsd; while ( !canZoomIn( board, board->zoomCount ) ) { - XP_ASSERT( board->zoomCount >= BOARD_ZOOMBY ); - board->zoomCount -= BOARD_ZOOMBY; + XP_ASSERT( board->zoomCount >= 1 ); + --board->zoomCount; } nCols = model_numCols( board->model ); @@ -3276,7 +3311,7 @@ boardTurnChanged( void* p_board ) nextPlayer = chooseBestSelPlayer( board ); if ( nextPlayer >= 0 ) { XP_U16 nHumans = gi_countLocalHumans( board->gi ); - selectPlayerImpl( board, nextPlayer, nHumans <= 1 ); + selectPlayerImpl( board, nextPlayer, nHumans <= 1, XP_TRUE ); } setTimerIf( board ); diff --git a/xwords4/common/board.h b/xwords4/common/board.h index 43e1dab39..084d72dc6 100644 --- a/xwords4/common/board.h +++ b/xwords4/common/board.h @@ -78,6 +78,11 @@ void board_reset( BoardCtxt* board ); XP_Bool board_setYOffset( BoardCtxt* board, XP_U16 newOffset ); XP_U16 board_getYOffset( const BoardCtxt* board ); +XP_U16 board_visTileCount( const BoardCtxt* board ); +XP_Bool board_canShuffle( const BoardCtxt* board ); +XP_Bool board_canTogglePending( const BoardCtxt* board ); +XP_Bool board_canHint( const BoardCtxt* board ); + /* zoomBy: >0: zoom in; < 0: zoom out; 0: query only */ XP_Bool board_zoom( BoardCtxt* board, XP_S16 zoomBy, XP_Bool* canInOut ); @@ -98,12 +103,13 @@ XP_Bool board_flip( BoardCtxt* board ); XP_Bool board_get_showValues( const BoardCtxt* board ); XP_Bool board_toggle_showValues( BoardCtxt* board ); XP_Bool board_replaceTiles( BoardCtxt* board ); +XP_Bool board_redoReplacedTiles( BoardCtxt* board ); XP_Bool board_requestHint( BoardCtxt* board, #ifdef XWFEATURE_SEARCHLIMIT XP_Bool useTileLimits, #endif - XP_Bool* workRemainsP ); + XP_Bool usePrev, XP_Bool* workRemainsP ); XP_Bool board_prefsChanged( BoardCtxt* board, CommonPrefs* cp ); diff --git a/xwords4/common/boardp.h b/xwords4/common/boardp.h index 927884c13..5a72bb9f8 100644 --- a/xwords4/common/boardp.h +++ b/xwords4/common/boardp.h @@ -58,11 +58,12 @@ typedef struct _DragState { DragType dtype; XP_Bool didMove; /* there was change during the drag; not a tap */ + XP_Bool cellChanged; /* nothing dragged but movement happened */ XP_Bool scrollTimerSet; XP_Bool isBlank; /* cache rather than lookup in model */ Tile tile; /* cache rather than lookup in model */ DragObjInfo start; - DragObjInfo cur; + DragObjInfo cur; /* where dragged object (not pen) is */ #ifdef XWFEATURE_RAISETILE XP_U16 yyAdd; #endif @@ -172,6 +173,7 @@ struct BoardCtxt { XP_Bool disableArrow; XP_Bool hideValsInTray; XP_Bool skipCommitConfirm; + XP_Bool allowPeek; /* Can look at non-turn player's rack */ XP_Bool eraseTray; XP_Bool boardObscuresTray; @@ -245,7 +247,7 @@ void figureTrayTileRect( BoardCtxt* board, XP_U16 index, XP_Rect* rect ); XP_Bool rectsIntersect( const XP_Rect* rect1, const XP_Rect* rect2 ); XP_S16 pointToTileIndex( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Bool* onDividerP ); -void board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer ); +void board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer, XP_Bool canPeek ); void flipIf( const BoardCtxt* board, XP_U16 col, XP_U16 row, XP_U16* fCol, XP_U16* fRow ); XP_Bool pointOnSomething( BoardCtxt* board, XP_U16 x, XP_U16 y, diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 245ba01df..855bc1651 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -135,6 +135,7 @@ typedef struct CommonPrefs { XP_U16 robotThinkMin, robotThinkMax; #endif XP_Bool showColors; /* applies to all games */ + XP_Bool allowPeek; /* applies to all games */ } CommonPrefs; #ifdef XWFEATURE_BLUETOOTH diff --git a/xwords4/common/dragdrpp.c b/xwords4/common/dragdrpp.c index e6f638db6..92a39686b 100644 --- a/xwords4/common/dragdrpp.c +++ b/xwords4/common/dragdrpp.c @@ -63,7 +63,16 @@ dragDropInProgress( const BoardCtxt* board ) XP_Bool dragDropHasMoved( const BoardCtxt* board ) { - return dragDropInProgress(board) && board->dragState.didMove; + XP_Bool moved = dragDropInProgress( board ); + if ( moved ) { + if ( board->dragState.didMove ) { + /* something was dragged; do nothing */ + } else { + const DragState* ds = &board->dragState; + moved = ds->cellChanged; /* did non-drag movement happen? */ + } + } + return moved; } /* dragDropHasMoved */ static XP_Bool @@ -490,14 +499,20 @@ dragDropContinueImpl( BoardCtxt* board, XP_U16 xx, XP_U16 yy, } else if ( ds->dtype == DT_BOARD ) { if ( newInfo.obj == OBJ_BOARD ) { XP_S16 diff = newInfo.u.board.col - ds->cur.u.board.col; + if ( !ds->cellChanged && 0 != diff ) { + ds->cellChanged = XP_TRUE; + } diff /= SCROLL_DRAG_THRESHHOLD; moving = adjustXOffset( board, diff ); diff = newInfo.u.board.row - ds->cur.u.board.row; + if ( !ds->cellChanged && 0 != diff ) { + ds->cellChanged = XP_TRUE; + } diff /= SCROLL_DRAG_THRESHHOLD; moving = adjustYOffset( board, diff ) || moving; } - } else { + } else if ( ds->dtype == DT_TILE ) { if ( newInfo.obj == OBJ_BOARD ) { moving = (newInfo.u.board.col != ds->cur.u.board.col) || (newInfo.u.board.row != ds->cur.u.board.row) @@ -537,6 +552,8 @@ dragDropContinueImpl( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_MEMCPY( &ds->cur, &newInfo, sizeof(ds->cur) ); startScrollTimerIf( board ); } + } else { + XP_ASSERT( 0 ); } if ( moving ) { diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c index 0a973daa4..801ce393d 100644 --- a/xwords4/common/engine.c +++ b/xwords4/common/engine.c @@ -31,7 +31,9 @@ extern "C" { typedef XP_U8 Engine_rack[MAX_UNIQUE_TILES+1]; -#define NUM_SAVED_MOVES 10 +#ifndef NUM_SAVED_ENGINE_MOVES +# define NUM_SAVED_ENGINE_MOVES 10 +#endif typedef struct BlankTuple { short col; @@ -48,11 +50,30 @@ typedef struct PossibleMove { blanks */ } PossibleMove; +/* MoveIterationData is a cache of moves so that next and prev searches don't + * always trigger an actual search. Instead we save up to + * NUM_SAVED_ENGINE_MOVES moves that sort together; then iteration is just + * returning the next or previous in the cache. The cache, savedMoves[], is + * sorted in increasing order, with any unused entries at the low end (since + * they sort as if score == 0). nInMoveCache is the actual number of entries. + * curCacheIndex is the index of the move most recently returned, or outside + * the range if nothing's been returned yet from the current cache. + * + * The cache is empty if nInMoveCache == 0, or if curCacheIndex is in a + * position that, given engine->usePrev, indicates it's been walked through + * the cache already rather than being poised to enter it. + */ + typedef struct MoveIterationData { - PossibleMove savedMoves[NUM_SAVED_MOVES]; - XP_U16 lowestSavedScore; + /* savedMoves: if any entries are unused (because result set doesn't fill, + they're at the low end (where sort'll put 'em) */ + PossibleMove savedMoves[NUM_SAVED_ENGINE_MOVES]; + //XP_U16 lowestSavedScore; PossibleMove lastSeenMove; - XP_U16 leftInMoveCache; + XP_U16 nInMoveCache; /* num entries, + 0 <= nInMoveCache < NUM_SAVED_ENGINE_MOVES */ + XP_U16 bottom; /* lowest non-0 entry */ + XP_S16 curCacheIndex; /* what we last returned */ } MoveIterationData; /* one bit per tile that's possible here *\/ */ @@ -66,6 +87,7 @@ struct EngineCtxt { Engine_rack rack; Tile blankTile; + XP_Bool usePrev; XP_Bool searchInProgress; XP_Bool searchHorizontal; XP_Bool isRobot; @@ -130,7 +152,10 @@ static void considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, BlankTuple* usedBlanks, XP_U16 usedBlanksCount ); static void saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ); - +static XP_Bool move_cache_empty( const EngineCtxt* engine ); +static void init_move_cache( EngineCtxt* engine ); +static PossibleMove* next_from_cache( EngineCtxt* engine ); +static void set_search_limits( EngineCtxt* engine ); #if defined __LITTLE_ENDIAN @@ -221,8 +246,9 @@ engine_makeFromStream( MPFORMAL XWStreamCtxt* XP_UNUSED_DBG(stream), void engine_reset( EngineCtxt* engine ) { - XP_MEMSET( &engine->miData, 0, sizeof(engine->miData) ); - engine->miData.lastSeenMove.score = 0xffff; /* max possible */ + XP_MEMSET( &engine->miData, 0, sizeof(engine->miData) ); + /* set last score to max possible */ + engine->miData.lastSeenMove.score = engine->usePrev? 0 : 0xffff; engine->searchInProgress = XP_FALSE; #ifdef XWFEATURE_SEARCHLIMIT engine->tileLimitsKnown = XP_FALSE; /* indicates not set */ @@ -273,11 +299,33 @@ cmpMoves( PossibleMove* m1, PossibleMove* m2 ) } /* cmpMoves */ #endif +#ifdef DEBUG +static void +print_savedMoves( const EngineCtxt* engine, const char* label ) +{ + int ii; + int pos = 0; + char buf[(NUM_SAVED_ENGINE_MOVES*10) + 3] = {0}; + for ( ii = 0; ii < NUM_SAVED_ENGINE_MOVES; ++ii ) { + if ( 0 < engine->miData.savedMoves[ii].score ) { + pos += XP_SNPRINTF( &buf[pos], VSIZE(buf)-pos, "[%d]: %d; ", + ii, engine->miData.savedMoves[ii].score ); + } + } + XP_LOGF( "%s: %s", label, buf ); +} +#else +# define print_savedMoves( engine, label ) +#endif + static XP_Bool chooseMove( EngineCtxt* engine, PossibleMove** move ) { - XP_U16 i; + XP_U16 ii; PossibleMove* chosen; + XP_Bool result; + + print_savedMoves( engine, "unsorted moves" ); /* First, sort 'em. Put the higher-scoring moves at the top where they'll get picked up first. Don't sort if we're working for a robot; we've @@ -286,53 +334,40 @@ chooseMove( EngineCtxt* engine, PossibleMove** move ) if ( engine->isRobot ) { chosen = &engine->miData.savedMoves[0]; } else { - while ( engine->miData.leftInMoveCache == 0 ) { - XP_Bool done = XP_TRUE; - for ( i = 0; i < NUM_SAVED_MOVES-1; ++i ) { - if ( CMPMOVES( &engine->miData.savedMoves[i], - &engine->miData.savedMoves[i+1]) > 0 ) { + XP_Bool done = !move_cache_empty( engine ); + while ( !done ) { /* while so can break */ + done = XP_TRUE; + PossibleMove* cur = engine->miData.savedMoves; + for ( ii = 0; ii < NUM_SAVED_ENGINE_MOVES-1; ++ii ) { + PossibleMove* next = cur + 1; + if ( CMPMOVES( cur, next ) > 0 ) { PossibleMove tmp; - XP_MEMCPY( &tmp, &engine->miData.savedMoves[i], - sizeof(tmp) ); - XP_MEMCPY( &engine->miData.savedMoves[i], - &engine->miData.savedMoves[i+1], - sizeof(engine->miData.savedMoves[i]) ); - XP_MEMCPY( &engine->miData.savedMoves[i+1], &tmp, - sizeof(engine->miData.savedMoves[i+1]) ); + XP_MEMCPY( &tmp, cur, sizeof(tmp) ); + XP_MEMCPY( cur, next, sizeof(*cur) ); + XP_MEMCPY( next, &tmp, sizeof(*next) ); done = XP_FALSE; } + cur = next; } if ( done ) { - engine->miData.leftInMoveCache = NUM_SAVED_MOVES; + init_move_cache( engine ); + print_savedMoves( engine, "sorted moves" ); } -#if 0 - XP_DEBUGF( "sorted moves; scores are: " ); - for ( i = 0; i < NUM_SAVED_MOVES; ++i ) { - XP_DEBUGF( "%d; ", engine->miData.savedMoves[i].score ); - } - XP_DEBUGF( "\n" ); -#endif } + /* now pick the one we're supposed to return */ - chosen = &engine->miData.savedMoves[--engine->miData.leftInMoveCache]; + chosen = next_from_cache( engine ); } *move = chosen; /* set either way */ - if ( chosen->score > 0 ) { + result = (NULL != chosen) && (chosen->score > 0); - if ( engine->miData.leftInMoveCache == 0 ) { - XP_MEMCPY( &engine->miData.lastSeenMove, - &engine->miData.savedMoves[0], - sizeof(engine->miData.lastSeenMove) ); - engine->miData.lowestSavedScore = 0; - } - - return XP_TRUE; - } else { + if ( !result ) { engine_reset( engine ); - return XP_FALSE; } + LOG_RETURNF( "%d", result ); + return result; } /* chooseMove */ /* Return of XP_TRUE means that we ran to completion. XP_FALSE means we were @@ -342,7 +377,7 @@ chooseMove( EngineCtxt* engine, PossibleMove** move ) XP_Bool engine_findMove( EngineCtxt* engine, const ModelCtxt* model, const DictionaryCtxt* dict, const Tile* tiles, - XP_U16 nTiles, + XP_U16 nTiles, XP_Bool usePrev, #ifdef XWFEATURE_SEARCHLIMIT const BdHintLimits* searchLimits, XP_Bool useTileLimits, @@ -384,6 +419,7 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model, engine->model = model; engine->dict = dict; + engine->usePrev = usePrev; engine->blankTile = dict_getBlankTile( dict ); engine->returnNOW = XP_FALSE; #ifdef XWFEATURE_SEARCHLIMIT @@ -408,7 +444,8 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model, engine->targetScore = targetScore; - if ( engine->miData.leftInMoveCache == 0 ) { + if ( move_cache_empty( engine ) ) { + set_search_limits( engine ); XP_MEMSET( engine->miData.savedMoves, 0, sizeof(engine->miData.savedMoves) ); @@ -476,12 +513,11 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model, result = XP_FALSE; } else { PossibleMove* move; - - result = XP_TRUE; - - (void)chooseMove( engine, &move ); - XP_ASSERT( !!newMove ); - XP_MEMCPY( newMove, &move->moveInfo, sizeof(*newMove) ); + result = chooseMove( engine, &move ); + if ( result ) { + XP_ASSERT( !!newMove ); + XP_MEMCPY( newMove, &move->moveInfo, sizeof(*newMove) ); + } } util_engineStopping( engine->util ); @@ -1043,7 +1079,7 @@ considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, XP_U16 lastRow, BlankTuple* usedBlanks, XP_U16 usedBlanksCount ) { - XP_U16 i; + XP_U16 ii; if ( blanksLeft == 0 ) { XP_U16 score; @@ -1059,11 +1095,11 @@ considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, if ( scoreQualifies( engine, score ) ) { posmove->score = score; XP_MEMSET( &posmove->blankVals, 0, sizeof(posmove->blankVals) ); - for ( i = 0; i < usedBlanksCount; ++i ) { - short col = usedBlanks[i].col; - posmove->blankVals[col] = usedBlanks[i].tile; + for ( ii = 0; ii < usedBlanksCount; ++ii ) { + short col = usedBlanks[ii].col; + posmove->blankVals[col] = usedBlanks[ii].tile; } - XP_ASSERT( posmove->moveInfo.isHorizontal== + XP_ASSERT( posmove->moveInfo.isHorizontal == engine->searchHorizontal ); posmove->moveInfo.commonCoord = (XP_U8)lastRow; saveMoveIfQualifies( engine, posmove ); @@ -1078,18 +1114,18 @@ considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, bt = &usedBlanks[usedBlanksCount++]; /* for each letter for which the blank might be standing in... */ - for ( i = 0; i < posmove->moveInfo.nTiles; ++i ) { - CellTile tile = posmove->moveInfo.tiles[i].tile; + for ( ii = 0; ii < posmove->moveInfo.nTiles; ++ii ) { + CellTile tile = posmove->moveInfo.tiles[ii].tile; if ( (tile & TILE_VALUE_MASK) == bTile && !IS_BLANK(tile) ) { - posmove->moveInfo.tiles[i].tile |= TILE_BLANK_BIT; - bt->col = i; + posmove->moveInfo.tiles[ii].tile |= TILE_BLANK_BIT; + bt->col = ii; bt->tile = bTile; considerScoreWordHasBlanks( engine, blanksLeft, posmove, lastRow, usedBlanks, usedBlanksCount ); /* now put things back */ - posmove->moveInfo.tiles[i].tile &= ~TILE_BLANK_BIT; + posmove->moveInfo.tiles[ii].tile &= ~TILE_BLANK_BIT; } } } @@ -1098,24 +1134,32 @@ considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, static void saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ) { - XP_S16 lowest = 0; + XP_S16 mostest = 0; + XP_S16 cmpVal; + XP_Bool usePrev = engine->usePrev; + XP_Bool foundEmpty = XP_FALSE; if ( !engine->isRobot ) { /* robot doesn't ask for next hint.... */ - + mostest = -1; /* we're not interested if we've seen this */ - if ( CMPMOVES( posmove, &engine->miData.lastSeenMove ) >= 0 ) { - lowest = -1; + cmpVal = CMPMOVES( posmove, &engine->miData.lastSeenMove ); + if ( !usePrev && cmpVal >= 0 ) { + /* XP_LOGF( "%s: dropping %d: higher than %d", __func__, */ + /* posmove->score, engine->miData.lastSeenMove.score ); */ + } else if ( usePrev && cmpVal <= 0 ) { + /* XP_LOGF( "%s: dropping %d: lower than %d", __func__, */ + /* posmove->score, engine->miData.lastSeenMove.score ); */ } else { - XP_S16 i; - /* terminate i at 1 because lowest starts at 0 */ - for ( lowest = NUM_SAVED_MOVES-1, i = lowest - 1; i >= 0; --i ) { - /* Find the lowest value move and overwrite it. Note that + XP_S16 ii; + /* terminate i at 1 because mostest starts at 0 */ + for ( ii = 0; ii < NUM_SAVED_ENGINE_MOVES; ++ii ) { + /* Find the mostest value move and overwrite it. Note that there might not be one, as all may have the same or higher scores and those that have the same score may compare higher. can't have this asssertion until I start noting the - lowest saved score (setting miData.lowestSavedScore) + mostest saved score (setting miData.mostestSavedScore) below. */ /* 1/20/2001 I don't see that this assertion is valid. I simply don't understand why it isn't tripped all the time @@ -1124,52 +1168,174 @@ saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ) /* || (engine->miData.savedMoves[i].score */ /* <= posmove->score) ); */ - if ( CMPMOVES( &engine->miData.savedMoves[lowest], - &engine->miData.savedMoves[i] ) > 0 ) { - lowest = i; + if ( 0 == engine->miData.savedMoves[ii].score ) { + foundEmpty = XP_TRUE; + mostest = ii; + break; + } else if ( -1 == mostest ) { + mostest = ii; + } else { + cmpVal = CMPMOVES( &engine->miData.savedMoves[mostest], + &engine->miData.savedMoves[ii] ); + if ( !usePrev && cmpVal > 0 ) { + mostest = ii; + } else if ( usePrev && cmpVal < 0 ) { + mostest = ii; + } } } } } - if ( lowest >= 0) { + + while ( mostest >= 0 ) { /* while: so we can break */ /* record the score we're dumping. No point in considering any scores lower than this for the rest of this round. */ - engine->miData.lowestSavedScore = - engine->miData.savedMoves[lowest].score; + /* engine->miData.lowestSavedScore = */ + /* engine->miData.savedMoves[lowest].score; */ /* XP_DEBUGF( "lowestSavedScore now %d\n", */ /* engine->miData.lowestSavedScore ); */ - if ( CMPMOVES( posmove, &engine->miData.savedMoves[lowest]) > 0 ) { - XP_MEMCPY( &engine->miData.savedMoves[lowest], posmove, - sizeof(engine->miData.savedMoves[lowest]) ); - /* XP_DEBUGF( "just saved move with score %d\n", */ - /* engine->miData.savedMoves[lowest].score ); */ + if ( foundEmpty ) { + /* we're good */ + } else { + cmpVal = CMPMOVES( posmove, &engine->miData.savedMoves[mostest]); + if ( !usePrev && cmpVal <= 0 ) { + break; + } else if ( usePrev && cmpVal >= 0 ) { + break; + } } + /* XP_LOGF( "saving move with score %d at %d (replacing %d)\n", */ + /* posmove->score, mostest, */ + /* engine->miData.savedMoves[mostest].score ); */ + XP_MEMCPY( &engine->miData.savedMoves[mostest], posmove, + sizeof(engine->miData.savedMoves[mostest]) ); + break; } } /* saveMoveIfQualifies */ +static void +set_search_limits( EngineCtxt* engine ) +{ + /* If we're going to be searching backwards we want our highest cached + move as the limit; otherwise the lowest */ + if ( 0 < engine->miData.nInMoveCache ) { + XP_U16 srcIndx = engine->usePrev + ? NUM_SAVED_ENGINE_MOVES-1 : engine->miData.bottom; + XP_MEMCPY( &engine->miData.lastSeenMove, + &engine->miData.savedMoves[srcIndx], + sizeof(engine->miData.lastSeenMove) ); + XP_LOGF( "%s: saved limit move with score: %d", __func__, + engine->miData.lastSeenMove.score ); + //engine->miData.lowestSavedScore = 0; + } else { + /* we're doing this for first time */ + engine_reset( engine ); + } +} + +static void +init_move_cache( EngineCtxt* engine ) +{ + XP_U16 nInMoveCache = NUM_SAVED_ENGINE_MOVES; + XP_U16 ii; + + for ( ii = 0; ii < NUM_SAVED_ENGINE_MOVES; ++ii ) { + if ( 0 == engine->miData.savedMoves[ii].score ) { + --nInMoveCache; + } else { + break; + } + } + engine->miData.nInMoveCache = nInMoveCache; + engine->miData.bottom = NUM_SAVED_ENGINE_MOVES - nInMoveCache; + + if ( engine->usePrev ) { + engine->miData.curCacheIndex = + NUM_SAVED_ENGINE_MOVES - nInMoveCache - 1; + } else { + engine->miData.curCacheIndex = NUM_SAVED_ENGINE_MOVES; + } + XP_LOGF( "%s: set curCacheIndex to %d", __func__, + engine->miData.curCacheIndex ); +} + +static PossibleMove* +next_from_cache( EngineCtxt* engine ) +{ + PossibleMove* move; + if ( move_cache_empty( engine ) ) { + move = NULL; + } else { + if ( engine->usePrev ) { + ++engine->miData.curCacheIndex; + } else { + --engine->miData.curCacheIndex; + } + move = &engine->miData.savedMoves[engine->miData.curCacheIndex]; + XP_LOGF( "%s: curCacheIndex now %d", __func__, + engine->miData.curCacheIndex ); + } + return move; +} + +static XP_Bool +move_cache_empty( const EngineCtxt* engine ) +{ + XP_Bool empty; + const MoveIterationData* miData = &engine->miData; + XP_LOGF( "%s: usePrev: %d; curCacheIndex: %d; nInMoveCache: %d", + __func__, engine->usePrev, miData->curCacheIndex, + miData->nInMoveCache ); + + if ( 0 == miData->nInMoveCache ) { + empty = XP_TRUE; + } else if ( engine->usePrev ) { + empty = miData->curCacheIndex >= NUM_SAVED_ENGINE_MOVES - 1; + } else { + empty = miData->curCacheIndex <= miData->bottom; + } + LOG_RETURNF( "%d", empty ); + return empty; +} + static XP_Bool scoreQualifies( EngineCtxt* engine, XP_U16 score ) { XP_Bool qualifies = XP_FALSE; + XP_Bool usePrev = engine->usePrev; - if ( (score > engine->miData.lastSeenMove.score) - || (score > engine->targetScore) - || (score < engine->miData.lowestSavedScore) ) { - /* do nothing */ + if ( score > engine->targetScore ) { + /* drop it */ + } else if ( usePrev && score < engine->miData.lastSeenMove.score ) { + /* drop it */ + } else if ( !usePrev && score > engine->miData.lastSeenMove.score + /* || (score < engine->miData.lowestSavedScore) */ ) { + /* drop it */ } else { - XP_S16 i; + XP_S16 ii; + PossibleMove* savedMoves = engine->miData.savedMoves; /* Look at each saved score, and return true as soon as one's found with a lower or equal score to this. As an optimization, consider remembering what the lowest score is *once there are - NUM_SAVED_MOVES moves in here* and doing a quick test on that. Or - better, keeping the list in sorted order. */ - for ( i = engine->isRobot? 0: NUM_SAVED_MOVES-1; i >= 0; --i ) { - if ( score >= engine->miData.savedMoves[i].score ) { + NUM_SAVED_ENGINE_MOVES moves in here* and doing a quick test on + that. Or better, keeping the list in sorted order. */ + for ( ii = 0, savedMoves = engine->miData.savedMoves; + ii < NUM_SAVED_ENGINE_MOVES; ++ii, ++savedMoves ) { + if ( savedMoves->score == 0 ) { /* empty slot */ qualifies = XP_TRUE; + } else if ( usePrev && score <= savedMoves->score ) { + qualifies = XP_TRUE; + break; + } else if ( !usePrev && score >= savedMoves->score ) { + qualifies = XP_TRUE; + break; + } + if ( engine->isRobot ) { /* we look at only one for robot */ break; } } } + //XP_LOGF( "%s(%d)->%d", __func__, score, qualifies ); return qualifies; } /* scoreQualifies */ diff --git a/xwords4/common/engine.h b/xwords4/common/engine.h index 6b090ab0c..e3e981754 100644 --- a/xwords4/common/engine.h +++ b/xwords4/common/engine.h @@ -51,7 +51,7 @@ void engine_destroy( EngineCtxt* ctxt ); #define NO_SCORE_LIMIT 10000 /* for targetScore */ XP_Bool engine_findMove( EngineCtxt* ctxt, const ModelCtxt* model, const DictionaryCtxt* dict, const Tile* tiles, - XP_U16 nTiles, + XP_U16 nTiles, XP_Bool usePrev, #ifdef XWFEATURE_SEARCHLIMIT const BdHintLimits* boardLimits, XP_Bool useTileLimits, diff --git a/xwords4/common/game.h b/xwords4/common/game.h index 3ad542de3..5b8820968 100644 --- a/xwords4/common/game.h +++ b/xwords4/common/game.h @@ -31,6 +31,7 @@ extern "C" { #endif +#define STREAM_VERS_NUNDONE 0x0C /* save undone tile in model */ #define STREAM_VERS_GAMESECONDS 0x0B /* save gameSeconds whether or not timer's enabled */ #define STREAM_VERS_4YOFFSET 0x0A /* 4 bits for yOffset on board */ @@ -46,7 +47,7 @@ extern "C" { #define STREAM_VERS_41B4 0x02 #define STREAM_VERS_405 0x01 -#define CUR_STREAM_VERS STREAM_VERS_GAMESECONDS +#define CUR_STREAM_VERS STREAM_VERS_NUNDONE typedef struct LocalPlayer { XP_UCHAR* name; diff --git a/xwords4/common/model.c b/xwords4/common/model.c index e991dd91a..9d340c2ed 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -70,7 +70,8 @@ static void buildModelFromStack( ModelCtxt* model, StackCtxt* stack, MovePrintFuncPost mpfpo, void* closure ); static void setPendingCounts( ModelCtxt* model, XP_S16 turn ); -static void loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); +static void loadPlayerCtxt( XWStreamCtxt* stream, XP_U16 version, + PlayerCtxt* pc ); static void writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); static XP_U16 model_getRecentPassCount( ModelCtxt* model ); @@ -136,7 +137,7 @@ model_makeFromStream( MPFORMAL XWStreamCtxt* stream, DictionaryCtxt* dict, (MovePrintFuncPost)NULL, NULL ); for ( i = 0; i < model->nPlayers; ++i ) { - loadPlayerCtxt( stream, &model->players[i] ); + loadPlayerCtxt( stream, version, &model->players[i] ); setPendingCounts( model, i ); invalidateScore( model, i ); } @@ -480,13 +481,14 @@ undoFromMoveInfo( ModelCtxt* model, XP_U16 turn, Tile blankTile, MoveInfo* mi ) setModelTileRaw( model, col, row, EMPTY_TILE ); notifyBoardListeners( model, turn, col, row, XP_FALSE ); + --model->vol.nTilesOnBoard; if ( IS_BLANK(tile) ) { tile = blankTile; } model_addPlayerTile( model, turn, -1, tile ); } - + XP_LOGF( "%s: %d tiles on board", __func__, model->vol.nTilesOnBoard ); adjustScoreForUndone( model, mi, turn ); } /* undoFromMoveInfo */ @@ -842,9 +844,9 @@ model_trayContains( ModelCtxt* model, XP_S16 turn, Tile tile ) } /* model_trayContains */ XP_U16 -model_getCurrentMoveCount( ModelCtxt* model, XP_S16 turn ) +model_getCurrentMoveCount( const ModelCtxt* model, XP_S16 turn ) { - PlayerCtxt* player; + const PlayerCtxt* player; XP_ASSERT( turn >= 0 ); player = &model->players[turn]; return player->nPending; @@ -989,6 +991,7 @@ model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, invalLastMove( model ); } + player->nUndone = 0; pt = &player->pendingTiles[player->nPending++]; XP_ASSERT( player->nPending <= MAX_TRAY_TILES ); @@ -1002,6 +1005,40 @@ model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, notifyBoardListeners( model, turn, col, row, XP_TRUE ); } /* model_moveTrayToBoard */ +XP_Bool +model_redoPendingTiles( ModelCtxt* model, XP_S16 turn ) +{ + XP_Bool changed = XP_FALSE; + + PlayerCtxt* player = &model->players[turn]; + XP_U16 nUndone = player->nUndone; + changed = nUndone > 0; + if ( changed ) { + PendingTile pendingTiles[nUndone]; + PendingTile* pt = pendingTiles; + + XP_MEMCPY( pendingTiles, &player->pendingTiles[player->nPending], + nUndone * sizeof(pendingTiles[0]) ); + + /* Now we have info about each tile, but don't know where in the + tray they are. So find 'em. */ + for ( pt = pendingTiles; nUndone-- > 0; ++pt ) { + Tile tile = pt->tile; + XP_Bool isBlank = 0 != (tile & TILE_BLANK_BIT); + XP_S16 foundAt; + + if ( isBlank ) { + tile = dict_getBlankTile( model->vol.dict ); + } + foundAt = model_trayContains( model, turn, tile ); + XP_ASSERT( foundAt >= 0 ); + model_moveTrayToBoard( model, turn, pt->col, pt->row, + foundAt, pt->tile & ~TILE_BLANK_BIT ); + } + } + return changed; +} + void model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, XP_U16 trayOffset ) @@ -1024,6 +1061,7 @@ model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, /* if we're called from putBackOtherPlayersTiles there may be nothing here */ if ( index < player->nPending ) { + PendingTile tmpPending; decrPendingTileCountAt( model, col, row ); notifyBoardListeners( model, turn, col, row, XP_FALSE ); @@ -1035,9 +1073,14 @@ model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, model_addPlayerTile( model, turn, trayOffset, tile ); --player->nPending; + tmpPending = player->pendingTiles[index]; for ( i = index; i < player->nPending; ++i ) { player->pendingTiles[i] = player->pendingTiles[i+1]; } + player->pendingTiles[player->nPending] = tmpPending; + + ++player->nUndone; + //XP_LOGF( "%s: nUndone(%d): %d", __func__, turn, player->nUndone ); if ( player->nPending == 0 ) { invalLastMove( model ); @@ -1104,6 +1147,30 @@ model_getNMoves( const ModelCtxt* model ) return result; } +XP_U16 +model_visTileCount( const ModelCtxt* model, XP_U16 turn, XP_Bool trayVisible ) +{ + XP_U16 count = model->vol.nTilesOnBoard; + if ( trayVisible ) { + count += model_getCurrentMoveCount( model, turn ); + } + return count; +} + +XP_Bool +model_canShuffle( const ModelCtxt* model, XP_U16 turn, XP_Bool trayVisible ) +{ + return trayVisible + && model_getPlayerTiles( model, turn )->nTiles > 1; +} + +XP_Bool +model_canTogglePending( const ModelCtxt* model, XP_U16 turn ) +{ + const PlayerCtxt* player = &model->players[turn]; + return 0 < player->nPending || 0 < player->nUndone; +} + static void incrPendingTileCountAt( ModelCtxt* model, XP_U16 col, XP_U16 row ) { @@ -1229,6 +1296,8 @@ commitTurn( ModelCtxt* model, XP_S16 turn, TrayTileSet* newTiles, setModelTileRaw( model, col, row, tile ); notifyBoardListeners( model, turn, col, row, XP_FALSE ); + + ++model->vol.nTilesOnBoard; } (void)getCurrentMoveScoreIfLegal( model, turn, stream, &score ); @@ -1241,12 +1310,14 @@ commitTurn( ModelCtxt* model, XP_S16 turn, TrayTileSet* newTiles, } player->nPending = 0; + player->nUndone = 0; newTilesP = newTiles->tiles; while ( nTiles-- ) { model_addPlayerTile( model, turn, -1, *newTilesP++ ); } + XP_LOGF( "%s: %d tiles on board", __func__, model->vol.nTilesOnBoard ); return score; } /* commitTurn */ @@ -1303,9 +1374,9 @@ model_getPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ) } /* model_getPlayerTile */ const TrayTileSet* -model_getPlayerTiles( ModelCtxt* model, XP_S16 turn ) +model_getPlayerTiles( const ModelCtxt* model, XP_S16 turn ) { - PlayerCtxt* player = &model->players[turn]; + const PlayerCtxt* player = &model->players[turn]; return (const TrayTileSet*)&player->trayTiles; } /* model_getPlayerTile */ @@ -1823,23 +1894,29 @@ model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, } /* model_getPlayersLastScore */ static void -loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) +loadPlayerCtxt( XWStreamCtxt* stream, XP_U16 version, PlayerCtxt* pc ) { - XP_U16 i; + PendingTile* pt; + XP_U16 nTiles; pc->curMoveValid = stream_getBits( stream, 1 ); traySetFromStream( stream, &pc->trayTiles ); pc->nPending = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + if ( STREAM_VERS_NUNDONE <= version ) { + pc->nUndone = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + } else { + XP_ASSERT( 0 == pc->nUndone ); + } - for ( i = 0; i < pc->nPending; ++i ) { - PendingTile* pt = &pc->pendingTiles[i]; + nTiles = pc->nPending + pc->nUndone; + for ( pt = pc->pendingTiles; nTiles-- > 0; ++pt ) { XP_U16 nBits; pt->col = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); pt->row = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); - nBits = (stream_getVersion( stream ) <= STREAM_VERS_RELAY) ? 6 : 7; + nBits = (version <= STREAM_VERS_RELAY) ? 6 : 7; pt->tile = (Tile)stream_getBits( stream, nBits ); } @@ -1848,21 +1925,22 @@ loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) static void writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) { - XP_U16 i; + XP_U16 nTiles; + PendingTile* pt; stream_putBits( stream, 1, pc->curMoveValid ); traySetToStream( stream, &pc->trayTiles ); stream_putBits( stream, NTILES_NBITS, pc->nPending ); + stream_putBits( stream, NTILES_NBITS, pc->nUndone ); - for ( i = 0; i < pc->nPending; ++i ) { - PendingTile* pt = &pc->pendingTiles[i]; + nTiles = pc->nPending + pc->nUndone; + for ( pt = pc->pendingTiles; nTiles-- > 0; ++pt ) { stream_putBits( stream, NUMCOLS_NBITS, pt->col ); stream_putBits( stream, NUMCOLS_NBITS, pt->row ); stream_putBits( stream, 7, pt->tile ); } - } /* writePlayerCtxt */ #ifdef CPLUS diff --git a/xwords4/common/model.h b/xwords4/common/model.h index 85fb1a071..054e9bdd1 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -137,7 +137,7 @@ void model_moveTileOnTray( ModelCtxt* model, XP_S16 turn, XP_S16 indexCur, /* As an optimization, return a pointer to the model's array of tiles for a player. Don't even think about modifying the array!!!! */ -const TrayTileSet* model_getPlayerTiles( ModelCtxt* model, XP_S16 turn ); +const TrayTileSet* model_getPlayerTiles( const ModelCtxt* model, XP_S16 turn ); void model_sortTiles( ModelCtxt* model, XP_S16 turn ); XP_U16 model_getNumTilesInTray( ModelCtxt* model, XP_S16 turn ); @@ -148,6 +148,8 @@ void model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, XP_S16 tileIndex, Tile blankFace ); XP_Bool model_moveTileOnBoard( ModelCtxt* model, XP_S16 turn, XP_U16 colCur, XP_U16 rowCur, XP_U16 colNew, XP_U16 rowNew ); +XP_Bool model_redoPendingTiles( ModelCtxt* model, XP_S16 turn ); + XP_S16 model_trayContains( ModelCtxt* model, XP_S16 turn, Tile tile ); @@ -160,7 +162,7 @@ XP_U16 model_numCols( const ModelCtxt* model ); void model_addToCurrentMove( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, Tile tile, XP_Bool isBlank ); -XP_U16 model_getCurrentMoveCount( ModelCtxt* model, XP_S16 turn ); +XP_U16 model_getCurrentMoveCount( const ModelCtxt* model, XP_S16 turn ); void model_getCurrentMoveTile( ModelCtxt* model, XP_S16 turn, XP_S16* index, Tile* tile, XP_U16* col, XP_U16* row, @@ -190,6 +192,13 @@ void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum, void model_resetCurrentTurn( ModelCtxt* model, XP_S16 turn ); XP_S16 model_getNMoves( const ModelCtxt* model ); +/* Are there two or more tiles visible */ +XP_U16 model_visTileCount( const ModelCtxt* model, XP_U16 turn, + XP_Bool trayVisible ); +XP_Bool model_canShuffle( const ModelCtxt* model, XP_U16 turn, + XP_Bool trayVisible ); +XP_Bool model_canTogglePending( const ModelCtxt* model, XP_U16 turn ); + /********************* notification ********************/ typedef void (*BoardListener)(void* data, XP_U16 turn, XP_U16 col, XP_U16 row, XP_Bool added ); diff --git a/xwords4/common/modelp.h b/xwords4/common/modelp.h index 40bbaaa0b..9330fca86 100644 --- a/xwords4/common/modelp.h +++ b/xwords4/common/modelp.h @@ -39,6 +39,7 @@ typedef struct PlayerCtxt { XP_Bool curMoveValid; TrayTileSet trayTiles; XP_U8 nPending; /* still in tray but "on board" */ + XP_U8 nUndone; /* tiles above nPending we can reuse */ PendingTile pendingTiles[MAX_TRAY_TILES]; } PlayerCtxt; @@ -53,6 +54,7 @@ typedef struct ModelVolatiles { void* trayListenerData; DictListener dictListenerFunc; void* dictListenerData; + XP_U16 nTilesOnBoard; MPSLOT } ModelVolatiles; diff --git a/xwords4/common/scorebdp.c b/xwords4/common/scorebdp.c index 4e452a31f..28b641b5c 100644 --- a/xwords4/common/scorebdp.c +++ b/xwords4/common/scorebdp.c @@ -324,7 +324,7 @@ handlePenUpScore( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) if ( rectNum == CURSOR_LOC_REM ) { util_remSelected( board->util ); } else if ( --rectNum >= 0 ) { - board_selectPlayer( board, rectNum ); + board_selectPlayer( board, rectNum, board->allowPeek ); } else { result = XP_FALSE; } diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 0119d1e3f..70517fa3c 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -676,7 +676,7 @@ makeRobotMove( ServerCtxt* server ) XP_ASSERT( !!server_getEngineFor( server, turn ) ); finished = engine_findMove( server_getEngineFor( server, turn ), model, model_getDictionary( model ), - tileSet->tiles, tileSet->nTiles, + tileSet->tiles, tileSet->nTiles, XP_FALSE, #ifdef XWFEATURE_SEARCHLIMIT NULL, XP_FALSE, #endif diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 4eaa8fecd..199b070f5 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -544,7 +544,7 @@ handleHint( CursesAppGlobals* globals ) #ifdef XWFEATURE_SEARCHLIMIT XP_FALSE, #endif - &redo ); + XP_FALSE, &redo ); return XP_TRUE; } /* handleHint */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index 93dfdbc37..e5afaf2b7 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -1,4 +1,4 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights reserved. * @@ -68,6 +68,7 @@ static void setCtrlsForTray( GtkAppGlobals* globals ); static void new_game( GtkWidget* widget, GtkAppGlobals* globals ); static void new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ); static void setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ); +static void disenable_buttons( GtkAppGlobals* globals ); #define GTK_TRAY_HT_ROWS 3 @@ -128,6 +129,7 @@ button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, event->x, event->y, &handled ); if ( redraw ) { board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); } } return 1; @@ -146,6 +148,7 @@ motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, event->y ); if ( handled ) { board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); } } else { handled = XP_FALSE; @@ -168,6 +171,7 @@ button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, event->y ); if ( redraw ) { board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); } globals->mouseDown = XP_FALSE; } @@ -991,6 +995,16 @@ makeMenus( GtkAppGlobals* globals, int XP_UNUSED(argc), return menubar; } /* makeMenus */ +static void +disenable_buttons( GtkAppGlobals* globals ) +{ + XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->flip_button, canFlip ); + + XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); +} + static gboolean handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) { @@ -1012,18 +1026,31 @@ handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) } /* handle_value_button */ static void -handle_hint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +handle_hint_button( GtkAppGlobals* globals, XP_Bool prev ) { XP_Bool redo; if ( board_requestHint( globals->cGlobals.game.board, #ifdef XWFEATURE_SEARCHLIMIT XP_FALSE, #endif - &redo ) ) { + prev, &redo ) ) { board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); } } /* handle_hint_button */ +static void +handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + handle_hint_button( globals, XP_TRUE ); +} /* handle_prevhint_button */ + +static void +handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + handle_hint_button( globals, XP_FALSE ); +} /* handle_nexthint_button */ + static void handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) { @@ -1034,7 +1061,7 @@ handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) #ifdef XWFEATURE_SEARCHLIMIT XP_TRUE, #endif - &redo ) ) { + XP_FALSE, &redo ) ) { board_draw( globals->cGlobals.game.board ); } } /* handle_nhint_button */ @@ -1071,6 +1098,16 @@ handle_redo_button( GtkWidget* XP_UNUSED(widget), { } /* handle_redo_button */ +static void +handle_toggle_undo( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* globals ) +{ + BoardCtxt* board = globals->cGlobals.game.board; + if ( board_redoReplacedTiles( board ) || board_replaceTiles( board ) ) { + board_draw( board ); + } +} + static void handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) { @@ -1670,13 +1707,17 @@ makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f", G_CALLBACK(handle_flip_button) ); gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + globals->flip_button = button; button = makeShowButtonFromBitmap( globals, "../value.xpm", "v", G_CALLBACK(handle_value_button) ); gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?", - G_CALLBACK(handle_hint_button) ); + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?-", + G_CALLBACK(handle_prevhint_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?+", + G_CALLBACK(handle_nexthint_button) ); gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n", @@ -1688,13 +1729,18 @@ makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); /* undo and redo buttons */ - button = makeShowButtonFromBitmap( globals, "../undo.xpm", "u", + button = makeShowButtonFromBitmap( globals, "../undo.xpm", "U", G_CALLBACK(handle_undo_button) ); gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../redo.xpm", "r", + button = makeShowButtonFromBitmap( globals, "../redo.xpm", "R", G_CALLBACK(handle_redo_button) ); gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "", "u/r", + G_CALLBACK(handle_toggle_undo) ); + globals->toggle_undo_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + /* the four buttons that on palm are beside the tray */ button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j", G_CALLBACK(handle_juggle_button) ); @@ -2058,6 +2104,7 @@ gtkmain( LaunchParams* params, int argc, char *argv[] ) globals.cp.skipCommitConfirm = params->skipCommitConfirm; globals.cp.sortNewTiles = params->sortNewTiles; globals.cp.showColors = params->showColors; + globals.cp.allowPeek = params->allowPeek; globals.cp.showRobotScores = params->showRobotScores; #ifdef XWFEATURE_SLOW_ROBOT globals.cp.robotThinkMin = params->robotThinkMin; diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index d61664bfb..4200fd8c8 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -87,8 +87,10 @@ typedef struct GtkAppGlobals { /* GdkPixmap* pixmap; */ GtkWidget* drawing_area; + GtkWidget* flip_button; GtkWidget* zoomin_button; GtkWidget* zoomout_button; + GtkWidget* toggle_undo_button; EngineCtxt* engine; diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 6885644e4..523af669c 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -200,7 +200,7 @@ usage( char* appName, char* msg ) "\t [-k] # ask for parameters via \"new games\" dlg\n" "\t [-h numRowsHidden] \n" # ifdef XWFEATURE_SEARCHLIMIT - "\t [-I] # don't support hint rect dragging\n" + "\t [-I] # support hint rect dragging\n" # endif #endif "\t [-f file] # use this file to save/load game\n" @@ -788,10 +788,11 @@ main( int argc, char** argv ) mainParams.nHidden = 0; mainParams.needsNewGame = XP_FALSE; #ifdef XWFEATURE_SEARCHLIMIT - mainParams.allowHintRect = XP_TRUE; + mainParams.allowHintRect = XP_FALSE; #endif mainParams.skipCommitConfirm = XP_TRUE; mainParams.showColors = XP_TRUE; + mainParams.allowPeek = XP_TRUE; /* serverName = mainParams.info.clientInfo.serverName = "localhost"; */ @@ -869,7 +870,7 @@ main( int argc, char** argv ) break; #ifdef XWFEATURE_SEARCHLIMIT case 'I': - mainParams.allowHintRect = XP_FALSE; + mainParams.allowHintRect = XP_TRUE; break; #endif case 'K': diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index e344dde94..db1a22744 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -59,6 +59,7 @@ typedef struct LaunchParams { XP_Bool verticalScore; XP_Bool hideValues; XP_Bool showColors; + XP_Bool allowPeek; XP_Bool sortNewTiles; XP_Bool skipCommitConfirm; XP_Bool needsNewGame; diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 02ba68760..d14e08f26 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -762,14 +762,21 @@ CookieRef::increasePlayerCounts( const CRefEvent* evt, bool reconn ) if ( reconn ) { if ( nPlayersS > 0 ) { if ( 0 != m_nPlayersSought ) { + logf( XW_LOGERROR, + "already have m_nPlayersSought: %d but new value %d", + m_nPlayersSought, nPlayersS ); goto drop; } m_nPlayersSought = nPlayersS; } - m_nPlayersHere += nPlayersH; - if ( 0 != m_nPlayersSought && m_nPlayersHere > m_nPlayersSought ) { + if ( 0 != m_nPlayersSought && + m_nPlayersHere + nPlayersH > m_nPlayersSought ) { + logf( XW_LOGERROR, "too many new players provided: %d > %d", + m_nPlayersHere + nPlayersH, m_nPlayersSought ); goto drop; } + + m_nPlayersHere += nPlayersH; if ( m_nPlayersHere == m_nPlayersSought ) { newevt.type = XWE_ALLHERE; } else { @@ -777,8 +784,11 @@ CookieRef::increasePlayerCounts( const CRefEvent* evt, bool reconn ) } addHost = true; } else if ( nPlayersS > 0 ) { /* a host; init values */ - assert( m_nPlayersSought == 0 ); - assert( m_nPlayersHere == 0 ); + if ( m_nPlayersSought != 0 || m_nPlayersHere != 0 ) { + logf( XW_LOGERROR, "cref in bad state: m_nPlayersSought: %d; " + "m_nPlayersHere: %d", m_nPlayersSought, m_nPlayersHere ); + goto drop; + } m_nPlayersHere = nPlayersH; m_nPlayersSought = nPlayersS; addHost = true;