Merge branch 'android_branch' into android_translate

This commit is contained in:
Eric House 2020-08-12 17:02:07 -07:00
commit e1a485d23e
14 changed files with 422 additions and 373 deletions

View file

@ -13,10 +13,10 @@
</style> </style>
</head> </head>
<body> <body>
<h2>CrossWords 4.4.162 release</h2> <h2>CrossWords 4.4.163 release</h2>
<p>This minor release adds a few translations and removes a feature <p>This release includes a major rewrite of the wordlist browser and
that prevented building for F-Droid.</p> fixes building for F-Droid.</p>
<div id="survey"> <div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take <p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -26,9 +26,10 @@
<h3>New with this release</h3> <h3>New with this release</h3>
<ul> <ul>
<li>Pull in translations (from Weblate)</li> <li>Add more capable filtering to the wordlist browser (Has
<li>Re-enable F-Droid builds by disabling the new networking a <a href="https://eehouse.org/xw4/faq.html">FAQ entry</a>
code (using MQTT -- yeah, TMI :-) </li> even.)</li>
<li>Enable networking via MQTT for the F-droid release</li>
</ul> </ul>
<p>(The full changelog <p>(The full changelog
@ -36,7 +37,6 @@
<h3>Coming soon</h3> <h3>Coming soon</h3>
<ul> <ul>
<li>Add filtering to the wordlist browser</li>
<li>Improve move-via-NFC</li> <li>Improve move-via-NFC</li>
<li>Support duplicate-style play (popular in France)</li> <li>Support duplicate-style play (popular in France)</li>
<li>Improve play-by-data-sms workaround <li>Improve play-by-data-sms workaround

View file

@ -701,10 +701,14 @@ public class DictBrowseDelegate extends DelegateBase
public void run() { public void run() {
stopProgress(); stopProgress();
m_browseState.onFilterAccepted( m_dict, null ); if ( null != wrapper ) {
initList( wrapper ); m_browseState.onFilterAccepted( m_dict, null );
setFindPats( m_browseState.m_pats ); initList( wrapper );
setFindPats( m_browseState.m_pats );
} else {
makeOkOnlyBuilder(R.string.alrt_bad_filter )
.show();
}
newFeatureAlert(); newFeatureAlert();
} }
} ); } );

View file

@ -554,7 +554,7 @@ public class MQTTUtils extends Thread implements IMqttActionListener, MqttCallba
notifyNotHere( context, from.mqtt_devID, gameID ); notifyNotHere( context, from.mqtt_devID, gameID );
} else { } else {
for ( long rowid : rowids ) { for ( long rowid : rowids ) {
MQTTMsgSink sink = new MQTTMsgSink( context, rowid ); MultiMsgSink sink = new MultiMsgSink( context, rowid );
helper.receiveMessage( rowid, sink, data ); helper.receiveMessage( rowid, sink, data );
} }
} }
@ -584,13 +584,6 @@ public class MQTTUtils extends Thread implements IMqttActionListener, MqttCallba
mReturnAddr = from; mReturnAddr = from;
} }
@Override
protected MultiMsgSink getSink( long rowid )
{
Context context = getContext();
return new MQTTMsgSink( context, rowid );
}
@Override @Override
void postNotification( String device, int gameID, long rowid ) void postNotification( String device, int gameID, long rowid )
{ {
@ -600,18 +593,10 @@ public class MQTTUtils extends Thread implements IMqttActionListener, MqttCallba
// GameUtils.postInvitedNotification( mContext, gameID, body, rowid ); // GameUtils.postInvitedNotification( mContext, gameID, body, rowid );
} }
private void receiveMessage( long rowid, MQTTMsgSink sink, byte[] msg ) private void receiveMessage( long rowid, MultiMsgSink sink, byte[] msg )
{ {
Log.d( TAG, "receiveMessage(rowid=%d, len=%d)", rowid, msg.length ); Log.d( TAG, "receiveMessage(rowid=%d, len=%d)", rowid, msg.length );
receiveMessage( rowid, sink, msg, mReturnAddr ); receiveMessage( rowid, sink, msg, mReturnAddr );
} }
} }
private static class MQTTMsgSink extends MultiMsgSink {
MQTTMsgSink( Context context, long rowid )
{
super( context, rowid );
}
}
} }

View file

@ -381,7 +381,7 @@ public class NFCUtils {
addReplyFor( new byte[]{REPLY_NOGAME}, gameID[0] ); addReplyFor( new byte[]{REPLY_NOGAME}, gameID[0] );
} else { } else {
for ( long rowid : rowids ) { for ( long rowid : rowids ) {
NFCMsgSink sink = new NFCMsgSink( context, rowid ); MultiMsgSink sink = new MultiMsgSink( context, rowid );
helper.receiveMessage( rowid, sink, body ); helper.receiveMessage( rowid, sink, body );
} }
} }
@ -1042,13 +1042,6 @@ public class NFCUtils {
super( context ); super( context );
} }
@Override
protected MultiMsgSink getSink( long rowid )
{
Context context = getContext();
return new NFCMsgSink( context, rowid );
}
@Override @Override
void postNotification( String device, int gameID, long rowid ) void postNotification( String device, int gameID, long rowid )
{ {
@ -1057,17 +1050,10 @@ public class NFCUtils {
GameUtils.postInvitedNotification( context, gameID, body, rowid ); GameUtils.postInvitedNotification( context, gameID, body, rowid );
} }
private void receiveMessage( long rowid, NFCMsgSink sink, byte[] msg ) private void receiveMessage( long rowid, MultiMsgSink sink, byte[] msg )
{ {
Log.d( TAG, "receiveMessage(rowid=%d, len=%d)", rowid, msg.length ); Log.d( TAG, "receiveMessage(rowid=%d, len=%d)", rowid, msg.length );
receiveMessage( rowid, sink, msg, mAddr ); receiveMessage( rowid, sink, msg, mAddr );
} }
} }
private static class NFCMsgSink extends MultiMsgSink {
NFCMsgSink( Context context, long rowid )
{
super( context, rowid );
}
}
} }

View file

@ -23,13 +23,14 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TableRow; import android.widget.TableRow;
import org.eehouse.android.xw4.jni.XwJNI.PatDesc; import org.eehouse.android.xw4.jni.XwJNI.PatDesc;
public class PatTableRow extends TableRow { public class PatTableRow extends TableRow {
private static final String TAG = PatTableRow.class.getSimpleName(); private static final String TAG = PatTableRow.class.getSimpleName();
private EditWClear mEdit; private EditText mEdit;
private CheckBox mCheck; private CheckBox mCheck;
public PatTableRow( Context context, AttributeSet as ) public PatTableRow( Context context, AttributeSet as )
@ -67,22 +68,14 @@ public class PatTableRow extends TableRow {
boolean handled = mEdit.hasFocus(); boolean handled = mEdit.hasFocus();
if ( handled ) { if ( handled ) {
mEdit.insertBlank( blank ); mEdit.getText().insert(mEdit.getSelectionStart(), blank );
} }
return handled; return handled;
} }
private void getFields() private void getFields()
{ {
for ( int ii = 0; mEdit = (EditText)Utils.getChildInstanceOf( this, EditText.class );
(null == mEdit || null == mCheck) && ii < getChildCount(); mCheck = (CheckBox)Utils.getChildInstanceOf( this, CheckBox.class );
++ii ) {
View view = getChildAt( ii );
if ( view instanceof EditWClear ) {
mEdit = (EditWClear)view;
} else if ( view instanceof CheckBox ) {
mCheck = (CheckBox)view;
}
}
} }
} }

View file

@ -50,7 +50,11 @@ abstract class XWServiceHelper {
Context getContext() { return mContext; } Context getContext() { return mContext; }
abstract MultiMsgSink getSink( long rowid ); MultiMsgSink getSink( long rowid )
{
return new MultiMsgSink( getContext(), rowid );
}
abstract void postNotification( String device, int gameID, long rowid ); abstract void postNotification( String device, int gameID, long rowid );
protected ReceiveResult receiveMessage( int gameID, protected ReceiveResult receiveMessage( int gameID,

View file

@ -619,9 +619,13 @@ public class XwJNI {
new Thread( new Runnable() { new Thread( new Runnable() {
@Override @Override
public void run() { public void run() {
IterWrapper wrapper = null;
long iterPtr = di_init( jniState, dictPtr, pats, long iterPtr = di_init( jniState, dictPtr, pats,
minLen, maxLen ); minLen, maxLen );
callback.onIterReady( new IterWrapper(iterPtr) ); if ( 0 != iterPtr ) {
wrapper = new IterWrapper(iterPtr);
}
callback.onIterReady( wrapper );
} }
} ).start(); } ).start();
} }

View file

@ -53,10 +53,12 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<View android:layout_width="5dp" <TextView style="@style/pat_table_label"
android:layout_height="5dp" android:visibility="invisible"
android:layout_span="2" />
/> <EditText style="@style/pat_table_edit"
android:visibility="invisible"
/>
<TextView android:layout_width="wrap_content" <TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/pat_col_anyorder" android:text="@string/pat_col_anyorder"
@ -69,9 +71,8 @@
<TextView style="@style/pat_table_label" <TextView style="@style/pat_table_label"
android:text="@string/pat_starts_with" android:text="@string/pat_starts_with"
/> />
<org.eehouse.android.xw4.EditWClear <EditText style="@style/pat_table_edit"
style="@style/pat_table_edit" />
/>
<CheckBox style="@style/pat_table_check" <CheckBox style="@style/pat_table_check"
/> />
</org.eehouse.android.xw4.PatTableRow> </org.eehouse.android.xw4.PatTableRow>
@ -82,9 +83,8 @@
<TextView style="@style/pat_table_label" <TextView style="@style/pat_table_label"
android:text="@string/pat_contains" android:text="@string/pat_contains"
/> />
<org.eehouse.android.xw4.EditWClear <EditText style="@style/pat_table_edit"
style="@style/pat_table_edit" />
/>
<CheckBox style="@style/pat_table_check" <CheckBox style="@style/pat_table_check"
/> />
</org.eehouse.android.xw4.PatTableRow> </org.eehouse.android.xw4.PatTableRow>
@ -95,9 +95,8 @@
<TextView style="@style/pat_table_label" <TextView style="@style/pat_table_label"
android:text="@string/pat_ends_with" android:text="@string/pat_ends_with"
/> />
<org.eehouse.android.xw4.EditWClear <EditText style="@style/pat_table_edit"
style="@style/pat_table_edit" />
/>
<CheckBox style="@style/pat_table_check" <CheckBox style="@style/pat_table_check"
/> />
</org.eehouse.android.xw4.PatTableRow> </org.eehouse.android.xw4.PatTableRow>

View file

@ -37,21 +37,21 @@
<!-- First state: is configured to use a room but has not yet <!-- First state: is configured to use a room but has not yet
contacted the relay and been assigned that room. --> contacted the relay and been assigned that room. -->
<string name="summary_relay_conf_fmt">Configured for room <string name="summary_relay_conf_fmt">Configured for room
\"%1$s\"</string> “%1$s”</string>
<!-- Second state: has been assigned to a room on the relay <!-- Second state: has been assigned to a room on the relay
(meaning the network is working) but there are not yet as (meaning the network is working) but there are not yet as
many players as expected: the game is not complete and play many players as expected: the game is not complete and play
cannot happen. --> cannot happen. -->
<string name="summary_relay_wait_fmt">Waiting for players in room <string name="summary_relay_wait_fmt">Waiting for players in room
\"%1$s\"</string> “%1$s”</string>
<!-- Third state: enough devices have connected in the room to <!-- Third state: enough devices have connected in the room to
form a complete game. We'll be in this state as long as the form a complete game. We'll be in this state as long as the
game exists. --> game exists. -->
<string name="summary_relay_conn_fmt">Game in play in room \"%1$s\"</string> <string name="summary_relay_conn_fmt">Game in play in room “%1$s”</string>
<!-- Final state: game is over. --> <!-- Final state: game is over. -->
<string name="summary_relay_gameover_fmt">Game over in room \"%1$s\"</string> <string name="summary_relay_gameover_fmt">Game over in room “%1$s”</string>
<string name="summary_invites_out">Players invited</string> <string name="summary_invites_out">Players invited</string>
<string name="summary_invites_out_fmt">Players invited to room \"%1$s\"</string> <string name="summary_invites_out_fmt">Players invited to room “%1$s”</string>
<!-- Games that have ended are listed with this string --> <!-- Games that have ended are listed with this string -->
<string name="gameOver">Game over</string> <string name="gameOver">Game over</string>
<!-- Otherwise they're listed with this to give some indication of <!-- Otherwise they're listed with this to give some indication of
@ -131,7 +131,7 @@
<!-- If you try to copy a networked game you get this error <!-- If you try to copy a networked game you get this error
message. --> message. -->
<string name="no_copy_network">Games that have already connected <string name="no_copy_network">Games that have already connected
to the relay cannot be copied. Use \"New from\" for a to the relay cannot be copied. Use “New from” for a
ready-to-play copy with all the same settings.</string> ready-to-play copy with all the same settings.</string>
<!-- --> <!-- -->
<string name="game_rename_title">Rename game</string> <string name="game_rename_title">Rename game</string>
@ -337,7 +337,7 @@
<!-- title of popup used to determine how words are handled that <!-- title of popup used to determine how words are handled that
are not in the wordlist used for the game (or player if using are not in the wordlist used for the game (or player if using
different wordlists per player) --> different wordlists per player) -->
<string name="phonies_spinner_prompt">How to handle \"phonies\" <string name="phonies_spinner_prompt">How to handle “phonies”
(words not in wordlist)</string> (words not in wordlist)</string>
<!-- These are the three choices in the popup above whose text is <!-- These are the three choices in the popup above whose text is
phonies_spinner_prompt --> phonies_spinner_prompt -->
@ -354,7 +354,7 @@
<!-- Shown when using the Game configure screen to configure a <!-- Shown when using the Game configure screen to configure a
networked game and you try to make all players local. --> networked game and you try to make all players local. -->
<string name="str_reg_server_sans_remote">At least one player must <string name="str_reg_server_sans_remote">At least one player must
be marked \"Remote\" for a game started as Host.</string> be marked “Remote” for a game started as Host.</string>
<!-- Used as the default name for remote players displayed within <!-- Used as the default name for remote players displayed within
the Game configure screen --> the Game configure screen -->
<string name="guest_name">(Off-device player)</string> <string name="guest_name">(Off-device player)</string>
@ -468,11 +468,11 @@
The number of players missing is substituted for "%1$d". --> The number of players missing is substituted for "%1$d". -->
<plurals name="invite_msg_fmt"> <plurals name="invite_msg_fmt">
<item quantity="one">This game is waiting for one remote <item quantity="one">This game is waiting for one remote
player. Would you like to invite someone to join -- assuming you player. Would you like to invite someone to joinassuming you
haven\'t already?</item> havent already?</item>
<item quantity="other">This game is waiting for %1$d remote <item quantity="other">This game is waiting for %1$d remote
players. Would you like to invite someone to join -- assuming players. Would you like to invite someone to joinassuming
you haven\'t already?</item> you havent already?</item>
</plurals> </plurals>
<plurals name="invite_sent_fmt"> <plurals name="invite_sent_fmt">
<item quantity="one">You have already invited a remote player to <item quantity="one">You have already invited a remote player to
@ -497,7 +497,7 @@
players to invite, IF the number of missing players is players to invite, IF the number of missing players is
greater than one this text is appended to the above. --> greater than one this text is appended to the above. -->
<string name="invite_multiple">\u0020(You are expecting multiple <string name="invite_multiple">\u0020(You are expecting multiple
remote players. You don\'t have to invite them all at once, but remote players. You dont have to invite them all at once, but
this alert will not be dismissed until everybody has been invited this alert will not be dismissed until everybody has been invited
and all invitations have been accepted.)</string> and all invitations have been accepted.)</string>
<string name="invit_expl_sms_fmt">Invite sent via Data SMS to phone <string name="invit_expl_sms_fmt">Invite sent via Data SMS to phone
@ -505,13 +505,13 @@
<string name="invit_expl_usrsms_fmt">Invite sent via SMS to %1$s on %2$s</string> <string name="invit_expl_usrsms_fmt">Invite sent via SMS to %1$s on %2$s</string>
<!-- shown when user chooses to invite via the default SMS app --> <!-- shown when user chooses to invite via the default SMS app -->
<string name="sms_invite_flakey">This new option launches your <string name="sms_invite_flakey">This new option launches your
default SMS app with an invitation ready to send -- when it default SMS app with an invitation ready to sendwhen it
works. Each SMS app is different, and some won\'t works. Each SMS app is different, and some wont
cooperate.</string> cooperate.</string>
<!-- Toast when invite-by-visible-sms fails --> <!-- Toast when invite-by-visible-sms fails -->
<string name="sms_invite_fail">Unable to launch SMS app</string> <string name="sms_invite_fail">Unable to launch SMS app</string>
<string name="invit_expl_bt_fmt">Invite sent via Bluetooth to <string name="invit_expl_bt_fmt">Invite sent via Bluetooth to
paired device \"%1$s\" on %2$s</string> paired device “%1$s” on %2$s</string>
<string name="invit_expl_relay_fmt">Invite forwarded by the relay <string name="invit_expl_relay_fmt">Invite forwarded by the relay
to another device on %1$s</string> to another device on %1$s</string>
<string name="invit_expl_notarget_fmt">Invite sent via %1$s on <string name="invit_expl_notarget_fmt">Invite sent via %1$s on
@ -528,15 +528,15 @@
only once per game. --> only once per game. -->
<plurals name="msg_relay_waiting_fmt"> <plurals name="msg_relay_waiting_fmt">
<item quantity="one">Device %1$d connected to relay in <item quantity="one">Device %1$d connected to relay in
room \"%2$s\". Waiting for one player.</item> room “%2$s”. Waiting for one player.</item>
<item quantity="other">Device %1$d connected to relay in <item quantity="other">Device %1$d connected to relay in
room \"%2$s\". Waiting for %3$d players.</item> room “%2$s”. Waiting for %3$d players.</item>
</plurals> </plurals>
<!-- Text of "toast" shown when a game is notified by the relay <!-- Text of "toast" shown when a game is notified by the relay
that all expected players have registered. At this point that all expected players have registered. At this point
play can begin. --> play can begin. -->
<string name="msg_relay_all_here_fmt">All players are here in room <string name="msg_relay_all_here_fmt">All players are here in room
\"%1$s\".</string> “%1$s”.</string>
<!-- Title of dialog used to alert players to relay-related <!-- Title of dialog used to alert players to relay-related
problems with the current game. --> problems with the current game. -->
<string name="relay_alert">Connection problem</string> <string name="relay_alert">Connection problem</string>
@ -557,7 +557,7 @@
relay. Once this happens there is no way to continue the relay. Once this happens there is no way to continue the
game so you might as well delete it (unless you're saving it game so you might as well delete it (unless you're saving it
for its history etc.) --> for its history etc.) -->
<string name="msg_dev_deleted_fmt">The game named \"%1$s\" has been <string name="msg_dev_deleted_fmt">The game named “%1$s” has been
deleted on another device. You will not be able to play any deleted on another device. You will not be able to play any
further.</string> further.</string>
<!-- Partial text of alert posted when phonies_warn or <!-- Partial text of alert posted when phonies_warn or
@ -943,7 +943,7 @@
<string name="peek_other">View tiles out-of-turn</string> <string name="peek_other">View tiles out-of-turn</string>
<!-- explanation of the above --> <!-- explanation of the above -->
<string name="peek_other_summary">Tapping on scoreboard name shows <string name="peek_other_summary">Tapping on scoreboard name shows
that player\'s tiles</string> that players tiles</string>
<!-- If this preference is checked the "crosshairs" (vertical and <!-- If this preference is checked the "crosshairs" (vertical and
horzontal lines through the cell your finger is on that help horzontal lines through the cell your finger is on that help
you tell where the app thinks you're actually tapping) will you tell where the app thinks you're actually tapping) will
@ -1018,7 +1018,7 @@
<string name="invite_choice_data_sms">Data SMS</string> <string name="invite_choice_data_sms">Data SMS</string>
<string name="invite_choice_email">Email</string> <string name="invite_choice_email">Email</string>
<string name="invite_choice_bt">Bluetooth</string> <string name="invite_choice_bt">Bluetooth</string>
<string name="invite_choice_nfc">NFC (\"Android beaming\")</string> <string name="invite_choice_nfc">NFC (“Android beaming”)</string>
<string name="invite_choice_relay">Internet/Relay</string> <string name="invite_choice_relay">Internet/Relay</string>
<string name="invite_choice_p2p">Wifi Direct</string> <string name="invite_choice_p2p">Wifi Direct</string>
<string name="invite_choice_title">Inviting players: How?</string> <string name="invite_choice_title">Inviting players: How?</string>
@ -1029,7 +1029,7 @@
<!-- <string name="nfc_or_email">Send invitation using NFC (Android --> <!-- <string name="nfc_or_email">Send invitation using NFC (Android -->
<!-- beaming &#8211; NEW) or via email?</string> --> <!-- beaming &#8211; NEW) or via email?</string> -->
<!-- <string name="nfc_or_sms_or_email">Send invitation using SMS --> <!-- <string name="nfc_or_sms_or_email">Send invitation using SMS -->
<!-- (texting) or NFC (\"Android beaming\" &#8211; NEW) or via email?</string> --> <!-- (texting) or NFC (“Android beaming” &#8211; NEW) or via email?</string> -->
<!-- When an invitation is sent, the user gets to choose between <!-- When an invitation is sent, the user gets to choose between
plaintext and html formatting. These two strings are shown in the plaintext and html formatting. These two strings are shown in the
two buttons in the dialog. --> two buttons in the dialog. -->
@ -1038,8 +1038,8 @@
<!-- <string name="button_nfc">NFC</string> --> <!-- <string name="button_nfc">NFC</string> -->
<!-- This is the subject line of the email/text sent to invite <!-- This is the subject line of the email/text sent to invite
someone to join a game. --> someone to join a game. -->
<string name="invite_subject">Let\'s play CrossWords</string> <string name="invite_subject">Lets play CrossWords</string>
<string name="invite_subject_fmt">Let\'s play CrossWords (room %1$s)</string> <string name="invite_subject_fmt">Lets play CrossWords (room %1$s)</string>
<!-- This is the body of the html version of the invitation. A URL <!-- This is the body of the html version of the invitation. A URL
is created with parameters describing the game and is created with parameters describing the game and
substituted for "%1$s". (The funky \u003c and friends are substituted for "%1$s". (The funky \u003c and friends are
@ -1130,7 +1130,7 @@
move)</string> move)</string>
<!-- Displyed when you try to commit a move and it's not your <!-- Displyed when you try to commit a move and it's not your
turn. --> turn. -->
<string name="str_not_your_turn">You can\'t do that; it\'s not <string name="str_not_your_turn">You cant do that; its not
your turn!</string> your turn!</string>
<!-- Displayed when you try to reveal a robot player's tiles, <!-- Displayed when you try to reveal a robot player's tiles,
either by tapping on its "hidden" rack (marked by "?" either by tapping on its "hidden" rack (marked by "?"
@ -1138,12 +1138,12 @@
ability to reveal another players tiles this way is ability to reveal another players tiles this way is
controlled by the peek_other preference and is disabled by controlled by the peek_other preference and is disabled by
default.)--> default.)-->
<string name="str_no_peek_robot_tiles">No peeking at the robot\'s <string name="str_no_peek_robot_tiles">No peeking at the robots
tiles!</string> tiles!</string>
<!-- Same as above, but used when you try to show tiles belonging <!-- Same as above, but used when you try to show tiles belonging
to a player on another device (a remote player.) --> to a player on another device (a remote player.) -->
<string name="str_no_peek_remote_tiles">No peeking at remote <string name="str_no_peek_remote_tiles">No peeking at remote
players\' tiles!</string> players tiles!</string>
<!-- Displayed when you try to begin a trade but there are not <!-- Displayed when you try to begin a trade but there are not
seven or more tiles in the pool. The rules don't allow seven or more tiles in the pool. The rules don't allow
trading in this case. --> trading in this case. -->
@ -1160,7 +1160,7 @@
there are no tiles on the board (no move has yet been made.) there are no tiles on the board (no move has yet been made.)
[If I'm being clever and disabling those features in this [If I'm being clever and disabling those features in this
case there may be no way to see this.] --> case there may be no way to see this.] -->
<string name="str_cant_undo_tileassign">Tile assignment can\'t be <string name="str_cant_undo_tileassign">Tile assignment cant be
undone.</string> undone.</string>
<!-- Using the hint feature is cheating by some players, and it <!-- Using the hint feature is cheating by some players, and it
can be disabled via the hints_allowed preference. I should can be disabled via the hints_allowed preference. I should
@ -1203,12 +1203,12 @@
downloading and not opening the game. This first message downloading and not opening the game. This first message
takes wordlist name and language substituted in for %1$ and takes wordlist name and language substituted in for %1$ and
%2$ --> %2$ -->
<string name="no_dict_fmt">Game \"%1$s\" requires a %2$s wordlist. <string name="no_dict_fmt">Game “%1$s” requires a %2$s wordlist.
Please download one before opening.</string> Please download one before opening.</string>
<!-- This is an alternative message presented when there's also <!-- This is an alternative message presented when there's also
the option of downloading another wordlist. Game name, the option of downloading another wordlist. Game name,
wordlist name and language are substituted in. --> wordlist name and language are substituted in. -->
<string name="no_dict_subst_fmt">Unable to open game \"%1$s\" because <string name="no_dict_subst_fmt">Unable to open game “%1$s” because
wordlist %2$s not found. (It may have been deleted, or stored wordlist %2$s not found. (It may have been deleted, or stored
on an external card that is no longer available.)\n\nYou can on an external card that is no longer available.)\n\nYou can
download a replacement or substitute another %3$s download a replacement or substitute another %3$s
@ -1233,7 +1233,7 @@
############################################################ ############################################################
--> -->
<!-- Text of dialog. Player name is substituted --> <!-- Text of dialog. Player name is substituted -->
<string name="msg_ask_password_fmt">Password for \"%1$s\":</string> <string name="msg_ask_password_fmt">Password for “%1$s”:</string>
<!-- used to create default names of games (when user has not <!-- used to create default names of games (when user has not
named them.) --> named them.) -->
<string name="game_fmt">Game %1$d</string> <string name="game_fmt">Game %1$d</string>
@ -1327,7 +1327,7 @@
########################################################### ###########################################################
--> -->
<!-- Title of New user info dialog--> <!-- Title of New user info dialog-->
<string name="newbie_title">Here\'s a tip</string> <string name="newbie_title">Heres a tip</string>
<!-- Text for button in new-user-info dialog with title just <!-- Text for button in new-user-info dialog with title just
above. --> above. -->
<string name="button_notagain">Do not show again</string> <string name="button_notagain">Do not show again</string>
@ -1388,7 +1388,7 @@
<!-- This is shown when you choose the board_menu_done menu item. <!-- This is shown when you choose the board_menu_done menu item.
It's to let you know that there's a shortcut that does almost It's to let you know that there's a shortcut that does almost
the same thing. --> the same thing. -->
<string name="not_again_done">Tapping the \"pts\" counter that <string name="not_again_done">Tapping the “pts” counter that
appears at the right end of the rack is the easiest way to appears at the right end of the rack is the easiest way to
commit a move.</string> commit a move.</string>
<!-- Shown in the Game configure screen when the game_locked <!-- Shown in the Game configure screen when the game_locked
@ -1422,7 +1422,7 @@
<!-- This is not currently shown --> <!-- This is not currently shown -->
<!-- <string name="not_again_dicts">CrossWords wordlists, which are --> <!-- <string name="not_again_dicts">CrossWords wordlists, which are -->
<!-- just compressed lists of words plus tile information, determine --> <!-- just compressed lists of words plus tile information, determine -->
<!-- what language a game is played in and how \"smart\" the robot --> <!-- what language a game is played in and how “smart” the robot -->
<!-- is. You can download different sized wordlists in many --> <!-- is. You can download different sized wordlists in many -->
<!-- languages here. Email me at eehouse@eehouse.org for information --> <!-- languages here. Email me at eehouse@eehouse.org for information -->
<!-- on building and installing your own wordlists.</string> --> <!-- on building and installing your own wordlists.</string> -->
@ -1430,19 +1430,23 @@
Board screen. The idea is that you're tapping around Board screen. The idea is that you're tapping around
figuring out how to play and when you tap an empty cell the figuring out how to play and when you tap an empty cell the
arrow appears. This explains it. --> arrow appears. This explains it. -->
<string name="not_again_arrow">Moving tiles to the board:\nYou can <string name="not_again_arrow">There are two ways to move tiles to
drag tiles between the rack and the board, or you can tap an the board.
empty square to place the board arrow. Rack tiles you tap will \n\n
replace the arrow (moving it one square in the direction it • You can drag them between the rack and the board.
points.) Tap the arrow once to change its orientation; a second \n\n
time, to hide it. A checkbox in the Appearance section of
Settings will hide it permanently.</string> • Or you can tap an empty square on the board to place the “board
arrow.” Then tiles in the rack replace the arrow when you tap
them. Tap the arrow to change its direction or to hide it.
</string>
<!-- Shown when the board screen is visible and it's just become <!-- Shown when the board screen is visible and it's just become
another players turn. The idea is to give a hint about how to another players turn. The idea is to give a hint about how to
find out about recent moves. --> find out about recent moves. -->
<string name="not_again_turnchanged">The player whose turn it is <string name="not_again_turnchanged">The player whose turn it is
is drawn large in the scoreboard.\n\nHold your finger on a name in is drawn large in the scoreboard.\n\nHold your finger on a name in
the scoreboard to get details about that player\'s most recent the scoreboard to get details about that players most recent
move.</string> move.</string>
<!-- Shown when you first pick the list_item_new_from menuitem --> <!-- Shown when you first pick the list_item_new_from menuitem -->
<string name="not_again_newfrom">Create a new ready-to-play game <string name="not_again_newfrom">Create a new ready-to-play game
@ -1465,7 +1469,7 @@
<!-- Welcome dialog text --> <!-- Welcome dialog text -->
<string name="default_name_message">Please enter your name <string name="default_name_message">Please enter your name
here. It will be used when creating new games. (You can change it here. It will be used when creating new games. (You can change it
later in the \"New game default\" section of Settings.)</string> later in the “New game default” section of Settings.)</string>
<!-- <!--
########################################################### ###########################################################
# :Dialogs: # :Dialogs:
@ -1493,8 +1497,8 @@
<!-- Another paragraph giving credit for work done other than by <!-- Another paragraph giving credit for work done other than by
Eric House and translators --> Eric House and translators -->
<string name="about_credits">Toolbar icons by Sarah Chu. Navbar <string name="about_credits">Toolbar icons by Sarah Chu. Navbar
icons from the Noun Project: \"archive\" by Trendy; \"rematch\" by icons from the Noun Project: “archive” by Trendy; “rematch” by
Becris; and \"swap\" by iconomania.</string> Becris; and “swap” by iconomania.</string>
<!-- text of dialog showing the set of changes made since the last <!-- text of dialog showing the set of changes made since the last
release --> release -->
<string name="changes_title">Recent changes</string> <string name="changes_title">Recent changes</string>
@ -1523,9 +1527,9 @@
<string name="not_again_lookup">This button lets you look up, <string name="not_again_lookup">This button lets you look up,
online, the words just played.</string> online, the words just played.</string>
<string name="not_again_archive">Archiving uses a special group <string name="not_again_archive">Archiving uses a special group
called \"Archive\" to store finished games you want to keep. And, called “Archive” to store finished games you want to keep. And,
since deleting an entire archive is easy, archiving is also a since deleting an entire archive is easy, archiving is also a
great way to mark games for deletion if that\'s what you prefer great way to mark games for deletion if thats what you prefer
to do.\n\n(Deleting the Archive group is safe because it will be to do.\n\n(Deleting the Archive group is safe because it will be
created anew when needed.) created anew when needed.)
</string> </string>
@ -1555,7 +1559,7 @@
but nothing can be done because there's not focussed text but nothing can be done because there's not focussed text
field --> field -->
<string name="blank_button_expl">This button inserts a blank at <string name="blank_button_expl">This button inserts a blank at
the active text field\'s cursor, but there is no cursor the active text fields cursor, but there is no cursor
now.</string> now.</string>
<!-- Label for first field in wordlist browser filter --> <!-- Label for first field in wordlist browser filter -->
@ -1588,7 +1592,7 @@
<string name="dict_browse_title_fmt">%1$s (%2$d words total)</string> <string name="dict_browse_title_fmt">%1$s (%2$d words total)</string>
<!-- --> <!-- -->
<string name="not_again_browse">This button opens the wordlist <string name="not_again_browse">This button opens the wordlist
browser on the current player\'s wordlist.</string> browser on the current players wordlist.</string>
<!-- --> <!-- -->
<string name="not_again_browseall">This button opens the wordlist <string name="not_again_browseall">This button opens the wordlist
browser on the wordlist of your choice.</string> browser on the wordlist of your choice.</string>
@ -1608,6 +1612,8 @@
<!-- Shown when user tries to make a wordlist filter where max < min --> <!-- Shown when user tries to make a wordlist filter where max < min -->
<string name="error_min_gt_max">The minimum length value cannot be <string name="error_min_gt_max">The minimum length value cannot be
greater than the maximum value.</string> greater than the maximum value.</string>
<!-- shown when something goes wrong building a filter -->
<string name="alrt_bad_filter">Unable to apply filter.</string>
<!-- Title of progress alert shown while wordlist is loading. For <!-- Title of progress alert shown while wordlist is loading. For
huge lists like Polish this can take a few seconds. --> huge lists like Polish this can take a few seconds. -->
@ -1617,11 +1623,10 @@
<!-- Text of alert shown when user first opens wordlist <!-- Text of alert shown when user first opens wordlist
browser. Shown until not-again checkbox checked. --> browser. Shown until not-again checkbox checked. -->
<string name="new_feature_filter">This wordlist browser now has <string name="new_feature_filter">You can now filter wordlists
dramatically improved filtering abilities. As an example, you can based on what they start with, end with, and/or contain. Access
show all words containing \"QU\" and ending in \"ING\".\n\nAccess filtering by tapping the Expander Arrow at the upper-right corner
filtering by tapping the Expander button at the upper-right corner of the window.\n\nRead more in the FAQ by tapping the button
of the window.\n\nRead more on the FAQ by tapping the button
below.</string> below.</string>
<string name="board_menu_file_email">Email author</string> <string name="board_menu_file_email">Email author</string>
@ -1631,7 +1636,7 @@
<string name="email_author_chooser">Send comment via</string> <string name="email_author_chooser">Send comment via</string>
<!-- --> <!-- -->
<string name="email_body_rev_fmt">(If relevant, please include the <string name="email_body_rev_fmt">(If relevant, please include the
version: \"%1$s\"; and make/model of your phone or version: “%1$s”; and make/model of your phone or
tablet.)</string> tablet.)</string>
<!-- <string name="newgame_enable_bt">Turn Bluetooth on</string> --> <!-- <string name="newgame_enable_bt">Turn Bluetooth on</string> -->
<!-- In invitation dialogs, button to remove checked items --> <!-- In invitation dialogs, button to remove checked items -->
@ -1660,11 +1665,11 @@
<string name="new_relay_body">Tap to open the new game</string> <string name="new_relay_body">Tap to open the new game</string>
<!-- --> <!-- -->
<string name="bt_bad_proto_fmt">The version of CrossWords on <string name="bt_bad_proto_fmt">The version of CrossWords on
\"%1$s\" is incompatible with this one for play using “%1$s” is incompatible with this one for play using
Bluetooth. One of you may need to upgrade before you can Bluetooth. One of you may need to upgrade before you can
continue.</string> continue.</string>
<string name="sms_bad_proto_fmt">The version of CrossWords on the <string name="sms_bad_proto_fmt">The version of CrossWords on the
phone with number \"%1$s\" is incompatible with this one for play phone with number “%1$s” is incompatible with this one for play
using Data SMS. One of you may need to upgrade before you can using Data SMS. One of you may need to upgrade before you can
continue.</string> continue.</string>
<!-- --> <!-- -->
@ -1710,15 +1715,15 @@
<!-- --> <!-- -->
<plurals name="invite_sms_desc_fmt"> <plurals name="invite_sms_desc_fmt">
<item quantity="one">Please check the phone number you want to <item quantity="one">Please check the phone number you want to
invite to your new game, then tap \"%2$s\".</item> invite to your new game, then tap “%2$s”.</item>
<item quantity="other">Please check the %1$d phone numbers you <item quantity="other">Please check the %1$d phone numbers you
want to invite to your new game, then tap \"%2$s\".</item> want to invite to your new game, then tap “%2$s”.</item>
</plurals> </plurals>
<!-- Appears near top of invitation phone number picker when it's <!-- Appears near top of invitation phone number picker when it's
the user-visible SMS case --> the user-visible SMS case -->
<string name="invite_sms_desc">Your SMS messaging app will then be <string name="invite_sms_desc">Your SMS messaging app will then be
launched with a message for you to send to the phone number launched with a message for you to send to the phone number
you\'ve selected.</string> youve selected.</string>
<!-- Appears near top of invitation phone number picker when it's <!-- Appears near top of invitation phone number picker when it's
the user-invisible Data SMS case --> the user-invisible Data SMS case -->
<string name="invite_nbs_desc">A data message will then be <string name="invite_nbs_desc">A data message will then be
@ -1728,28 +1733,28 @@
<!-- --> <!-- -->
<plurals name="invite_relay_desc_fmt"> <plurals name="invite_relay_desc_fmt">
<item quantity="one">Please check the device you want to invite <item quantity="one">Please check the device you want to invite
to your new game, then tap \"%2$s\".</item> to your new game, then tap “%2$s”.</item>
<item quantity="other">Please check the %1$d devices you want to invite <item quantity="other">Please check the %1$d devices you want to invite
to your new game, then tap \"%2$s\".</item> to your new game, then tap “%2$s”.</item>
</plurals> </plurals>
<!-- --> <!-- -->
<plurals name="invite_p2p_desc_fmt"> <plurals name="invite_p2p_desc_fmt">
<item quantity="one">Please select the name of the <item quantity="one">Please select the name of the
WiFiDirect device you want to invite to your new game, then tap WiFiDirect device you want to invite to your new game, then tap
\"%2$s\".</item> “%2$s”.</item>
<item quantity="other">Please select the %1$d WiFiDirect device names you <item quantity="other">Please select the %1$d WiFiDirect device names you
want to invite to your new game, then tap \"%2$s\".</item> want to invite to your new game, then tap “%2$s”.</item>
</plurals> </plurals>
<string name="invite_p2p_desc_extra">Only devices that are <string name="invite_p2p_desc_extra">Only devices that are
currently available are shown. If a nearby device isn\'t showing currently available are shown. If a nearby device isnt showing
up please make sure that WiFi is turned on, that CrossWords is up please make sure that WiFi is turned on, that CrossWords is
installed, and that play via WiFi Direct is enabled.</string> installed, and that play via WiFi Direct is enabled.</string>
<!-- --> <!-- -->
<string name="warn_nomobile_fmt">The number %1$s for %2$s is not <string name="warn_nomobile_fmt">The number %1$s for %2$s is not
a \"mobile\" number. Import anyway?</string> a “mobile” number. Import anyway?</string>
<!-- Shows in SMS Invite dialog when no phone numbers have been saved previously --> <!-- Shows in SMS Invite dialog when no phone numbers have been saved previously -->
<string name="empty_sms_inviter">This phone list is empty. Use the <string name="empty_sms_inviter">This phone list is empty. Use the
\"Import contact\" button to add people you want to invite, the + “Import contact” button to add people you want to invite, the +
button to enter numbers directly.</string> button to enter numbers directly.</string>
<string name="empty_bt_inviter">No paired devices detected yet.</string> <string name="empty_bt_inviter">No paired devices detected yet.</string>
<!-- Shows in WiFiDirect Invite dialog when no known peers --> <!-- Shows in WiFiDirect Invite dialog when no known peers -->
@ -1757,8 +1762,8 @@
reachable via WiFiDirect that have CrossWords installed.</string> reachable via WiFiDirect that have CrossWords installed.</string>
<!-- --> <!-- -->
<string name="empty_relay_inviter">This list of devices is <string name="empty_relay_inviter">This list of devices is
empty. Use the \"Scan games\" button to scan your old games empty. Use the “Scan games” button to scan your old games
for opponents. Use the \"+\" button to enter device IDs directly.</string> for opponents. Use the “+” button to enter device IDs directly.</string>
<!-- --> <!-- -->
<string name="get_sms_title">Manual entry</string> <string name="get_sms_title">Manual entry</string>
<string name="get_sms_number">Device phone number:</string> <string name="get_sms_number">Device phone number:</string>
@ -1790,7 +1795,7 @@
<string name="summary_conn_sms_fmt">Game in play with %1$s</string> <string name="summary_conn_sms_fmt">Game in play with %1$s</string>
<!-- --> <!-- -->
<string name="warn_unlimited">Are you certain this number is on an <string name="warn_unlimited">Are you certain this number is on an
account with unlimited texting? Tap \"Cancel\" if you are account with unlimited texting? Tap “Cancel” if you are
not.</string> not.</string>
<!-- --> <!-- -->
<string name="remote_undone">Remote device undid a turn.</string> <string name="remote_undone">Remote device undid a turn.</string>
@ -1816,7 +1821,7 @@
<string name="summary_busy">Summary unavailable</string> <string name="summary_busy">Summary unavailable</string>
<string name="connstat_net_noaddr">This networked game has no way <string name="connstat_net_noaddr">This networked game has no way
to connect and cannot be played.\n\n(It was probably created from to connect and cannot be played.\n\n(It was probably created from
an invitation that didn\'t specify any way of connecting that your an invitation that didnt specify any way of connecting that your
device supports, or you may have recently removed its last way of device supports, or you may have recently removed its last way of
connecting.)</string> connecting.)</string>
<!-- --> <!-- -->
@ -1854,7 +1859,7 @@
<string name="confirm_sms_expl">Warning: This feature is meant for <string name="confirm_sms_expl">Warning: This feature is meant for
phones with unlimited texting plans. Once you enable it dozens of phones with unlimited texting plans. Once you enable it dozens of
Data SMS messages will be sent (invisibly) for each game Data SMS messages will be sent (invisibly) for each game
played. If you don\'t have an unlimited plan your carrier may played. If you dont have an unlimited plan your carrier may
charge you for each and every message!\n\nShould play via Data SMS charge you for each and every message!\n\nShould play via Data SMS
be enabled?</string> be enabled?</string>
<!-- --> <!-- -->
@ -1864,7 +1869,7 @@
<!-- --> <!-- -->
<string name="confirm_sms_unlimited">Yes: I have unlimited texting</string> <string name="confirm_sms_unlimited">Yes: I have unlimited texting</string>
<!-- --> <!-- -->
<string name="confirm_sms_willpay">Yes: I\'ll pay all carrier charges</string> <string name="confirm_sms_willpay">Yes: Ill pay all carrier charges</string>
<!-- --> <!-- -->
<string name="warn_sms_disabled">Play via Data SMS is currently <string name="warn_sms_disabled">Play via Data SMS is currently
disabled. No moves will be sent via Data SMS.\n\nYou can enable disabled. No moves will be sent via Data SMS.\n\nYou can enable
@ -1978,7 +1983,7 @@
<string name="button_archive">Archive\u200C</string> <string name="button_archive">Archive\u200C</string>
<string name="checkbox_archive">Move to Archive</string> <string name="checkbox_archive">Move to Archive</string>
<string name="group_name_archive">Archive</string> <string name="group_name_archive">Archive</string>
<string name="duplicate_group_name_fmt">The group \"%1$s\" already exists.</string> <string name="duplicate_group_name_fmt">The group “%1$s” already exists.</string>
<string name="button_reconnect">Reconnect</string> <string name="button_reconnect">Reconnect</string>
<string name="square_tiles">Square rack tiles</string> <string name="square_tiles">Square rack tiles</string>
<string name="square_tiles_summary">Even if they can be taller</string> <string name="square_tiles_summary">Even if they can be taller</string>
@ -1987,7 +1992,7 @@
<string name="not_again_newselect">Tapping a game opens it.\n\nYou <string name="not_again_newselect">Tapping a game opens it.\n\nYou
can instead tap the icons at the left to select or deselect games, can instead tap the icons at the left to select or deselect games,
then act on selected games, e.g. to delete them, using the menu or then act on selected games, e.g. to delete them, using the menu or
\"Actionbar.\"</string> “Actionbar.”</string>
<string name="not_again_backclears">The back button clears any <string name="not_again_backclears">The back button clears any
selection instead of exiting. Hit it again to exit the selection instead of exiting. Hit it again to exit the
app.</string> app.</string>
@ -2110,14 +2115,14 @@
wordlists and view the ones you already have.\n\nWhat wordlists wordlists and view the ones you already have.\n\nWhat wordlists
you have installed determines:\n• What languages you can play you have installed determines:\n• What languages you can play
in\n• How smart the robot player is\n• What words are in\n• How smart the robot player is\n• What words are
legal\n\nCheck the \"Show downloadable\" box at the top to see legal\n\nCheck the “Show downloadable” box at the top to see
what\'s available.</string> whats available.</string>
<string name="force_tablet_title">Use tablet (side-by-side) layout?</string> <string name="force_tablet_title">Use tablet (side-by-side) layout?</string>
<string name="force_tablet_default">Use default for my device</string> <string name="force_tablet_default">Use default for my device</string>
<string name="force_tablet_tablet">Force tablet layout</string> <string name="force_tablet_tablet">Force tablet layout</string>
<string name="force_tablet_phone">Force phone layout</string> <string name="force_tablet_phone">Force phone layout</string>
<!-- Nagging: title of notification reminder message --> <!-- Nagging: title of notification reminder message -->
<string name="nag_title">Reminder: It\'s your turn</string> <string name="nag_title">Reminder: Its your turn</string>
<!-- body of warning notification reminder message. First three <!-- body of warning notification reminder message. First three
are used to build a string based on the length of time that's then are used to build a string based on the length of time that's then
inserted in the fourth. E.g "PlayerName moved more than 2 day[s], inserted in the fourth. E.g "PlayerName moved more than 2 day[s],
@ -2186,7 +2191,7 @@
<item quantity="other">%1$d players</item> <item quantity="other">%1$d players</item>
</plurals> </plurals>
<string name="err_dup_invite_fmt">Duplicate invitation rejected: <string name="err_dup_invite_fmt">Duplicate invitation rejected:
device \"%1$s\" has already accepted an invitation to this device “%1$s” has already accepted an invitation to this
game.</string> game.</string>
<string name="network_advanced_title">Advanced</string> <string name="network_advanced_title">Advanced</string>
<string name="network_advanced_summary">For experienced players</string> <string name="network_advanced_summary">For experienced players</string>
@ -2209,7 +2214,7 @@
<!-- Button for alert with title above --> <!-- Button for alert with title above -->
<string name="button_close">Close</string> <string name="button_close">Close</string>
<string name="button_reinvite">Re-invite</string> <string name="button_reinvite">Re-invite</string>
<string name="invite_stays">(This dialog will stay up until all <string name="invite_stays">(You will see this dialog until all
remote players have connected. You can close the game if you remote players have connected. You can close the game if you
expect it to take a while. Remote players will still be able to expect it to take a while. Remote players will still be able to
connect, and you will be notified when they do.)</string> connect, and you will be notified when they do.)</string>
@ -2232,7 +2237,7 @@
<string name="disable_nags_title">Turn reminders</string> <string name="disable_nags_title">Turn reminders</string>
<string name="disable_nag_title">Disable network game reminders</string> <string name="disable_nag_title">Disable network game reminders</string>
<string name="disable_nag_summary">Do not notify me no matter <string name="disable_nag_summary">Do not notify me no matter
how long it\'s been my turn</string> how long its been my turn</string>
<string name="disable_nag_solo_title">Disable solo game reminders</string> <string name="disable_nag_solo_title">Disable solo game reminders</string>
<string name="confirm_get_locdict_fmt">Your device is set up for <string name="confirm_get_locdict_fmt">Your device is set up for
%1$s. Would you like to download a wordlist so you can play %1$s. Would you like to download a wordlist so you can play
@ -2271,7 +2276,7 @@
<string name="enable_sms_toself_title">Short-circuit SMS to self</string> <string name="enable_sms_toself_title">Short-circuit SMS to self</string>
<string name="enable_sms_toself_summary">Skip radio when phone numbers same</string> <string name="enable_sms_toself_summary">Skip radio when phone numbers same</string>
<string name="force_radio_title">Pretend to have radio</string> <string name="force_radio_title">Pretend to have radio</string>
<string name="radio_name_real">Don\'t pretend</string> <string name="radio_name_real">Dont pretend</string>
<string name="radio_name_tablet">Tablet/no radio</string> <string name="radio_name_tablet">Tablet/no radio</string>
<string name="radio_name_gsm">GSM</string> <string name="radio_name_gsm">GSM</string>
<string name="radio_name_cdma">CDMA</string> <string name="radio_name_cdma">CDMA</string>
@ -2283,7 +2288,7 @@
<string name="expl_relay_url">URL for relay web API</string> <string name="expl_relay_url">URL for relay web API</string>
<string name="expl_mqtt_url">URL for new MQTT-based relay API</string> <string name="expl_mqtt_url">URL for new MQTT-based relay API</string>
<string name="got_langdict_title">Fetch default wordlist for language</string> <string name="got_langdict_title">Fetch default wordlist for language</string>
<string name="got_langdict_summary">Don\'t try a second time</string> <string name="got_langdict_summary">Dont try a second time</string>
<string name="pref_group_sms_title">SMS Stuff</string> <string name="pref_group_sms_title">SMS Stuff</string>
<string name="pref_group_sms_summary">Prefs related to play-via-sms</string> <string name="pref_group_sms_summary">Prefs related to play-via-sms</string>
<string name="pref_group_relay_title">Relay Stuff</string> <string name="pref_group_relay_title">Relay Stuff</string>
@ -2301,11 +2306,11 @@
connect.\n\nPlease try opening the game again later after connect.\n\nPlease try opening the game again later after
conditions have changed.</string> conditions have changed.</string>
<string name="drop_relay_warning_fmt">(This game can also connect <string name="drop_relay_warning_fmt">(This game can also connect
via %1$s, so if you\'d like to play the game without a relay via %1$s, so if youd like to play the game without a relay
connection you can.) connection you can.)
</string> </string>
<string name="confirm_drop_relay">Are you sure you want to drop this <string name="confirm_drop_relay">Are you sure you want to drop this
game\'s ability to communicate via the relay?</string> games ability to communicate via the relay?</string>
<string name="wifi_warning">\u0020For example, you may need to be on <string name="wifi_warning">\u0020For example, you may need to be on
a different WiFi network.</string> a different WiFi network.</string>
<string name="confirm_drop_relay_bt">Bluetooth only works for nearby <string name="confirm_drop_relay_bt">Bluetooth only works for nearby
@ -2313,27 +2318,27 @@
<string name="confirm_drop_relay_sms">Not all carriers support play <string name="confirm_drop_relay_sms">Not all carriers support play
via Data SMS.</string> via Data SMS.</string>
<string name="button_enable">Enable</string> <string name="button_enable">Enable</string>
<string name="not_again_comms_relay">The \"relay\" is a server on <string name="not_again_comms_relay">The “relay” is a server on
the internet that passes messages between devices that are running the internet that passes messages between devices that are running
CrossWords. It works any time you have a fully-functional internet CrossWords. It works any time you have a fully-functional internet
connection, but might have problems on restricted WiFi connection, but might have problems on restricted WiFi
networks.</string> networks.</string>
<string name="not_again_comms_sms">Play via Data SMS uses the <string name="not_again_comms_sms">Play via Data SMS uses the
technology on which \"texting\" is built. Though the messages are technology on which “texting” is built. Though the messages are
invisible to you, your carrier bills them as texts, so you want to invisible to you, your carrier bills them as texts, so you want to
avoid this feature unless you have an unlimited texting plan (or avoid this feature unless you have an unlimited texting plan (or
budget.) Note that Android only supports this feature on devices on budget.) Note that Android only supports this feature on devices on
a GSM carrier, i.e. every carrier in the world except Verizon and a GSM carrier, i.e. every carrier in the world except Verizon and
Sprint.</string> Sprint.</string>
<string name="not_again_comms_bt">Use Bluetooth to play against a <string name="not_again_comms_bt">Use Bluetooth to play against a
nearby device that\'s \"paired\" with yours.</string> nearby device thats “paired” with yours.</string>
<string name="not_again_comms_p2p">Use WiFi Direct to play against a <string name="not_again_comms_p2p">Use WiFi Direct to play against a
nearby WiFi Direct-capable device with CrossWords installed.</string> nearby WiFi Direct-capable device with CrossWords installed.</string>
<string name="str_no_hint_found">Cannot find any moves</string> <string name="str_no_hint_found">Cannot find any moves</string>
<string name="not_again_rematch_two_only">Rematch is limited to <string name="not_again_rematch_two_only">Rematch is limited to
two-person games, at least for now, because it\'s harder with more two-person games, at least for now, because its harder with more
devices and I think it\'s rare that people play with more than devices and I think its rare that people play with more than
two. Let me know if I\'m wrong and I\'ll up the priority.</string> two. Let me know if Im wrong and Ill up the priority.</string>
<string name="show_sms_title">Show SMS sends, receives</string> <string name="show_sms_title">Show SMS sends, receives</string>
<string name="show_fcm_title">Show FCM receives</string> <string name="show_fcm_title">Show FCM receives</string>
<!-- Shown after "resend messages" menuitem chosen --> <!-- Shown after "resend messages" menuitem chosen -->
@ -2347,7 +2352,7 @@
<string name="clip_label">Invitation URL</string> <string name="clip_label">Invitation URL</string>
<!-- EXPERIMENTAL: Newbie hint next when invite_choice_clip shown <!-- EXPERIMENTAL: Newbie hint next when invite_choice_clip shown
when chosen --> when chosen -->
<string name="not_again_clip_expl_fmt">The \"%1$s\" option copies an <string name="not_again_clip_expl_fmt">The “%1$s” option copies an
invitation URL to the clipboard. Paste it into the app of your invitation URL to the clipboard. Paste it into the app of your
choice and send it to your friend.</string> choice and send it to your friend.</string>
<string name="confirm_clear_chat">Are you sure you want to delete <string name="confirm_clear_chat">Are you sure you want to delete
@ -2361,7 +2366,7 @@
<!-- Debug-only item for testing corrupt game warnings --> <!-- Debug-only item for testing corrupt game warnings -->
<string name="list_item_markbad">Mark corrupt</string> <string name="list_item_markbad">Mark corrupt</string>
<string name="not_again_dfltname_fmt">You are using the default <string name="not_again_dfltname_fmt">You are using the default
player name \"%1$s\". Would you like to personalize with your own player name “%1$s”. Would you like to personalize with your own
name before you create this game?</string> name before you create this game?</string>
<string name="no_invites">This game has sent no invitations</string> <string name="no_invites">This game has sent no invitations</string>
<string name="after_restart">This change will take effect after you <string name="after_restart">This change will take effect after you
@ -2382,25 +2387,25 @@
\n\n \n\n
You can read more using the button below.</string> You can read more using the button below.</string>
<string name="download_rationale">CrossWords needs access to <string name="download_rationale">CrossWords needs access to
temporary storage to keep what you\'re about to download. temporary storage to keep what youre about to download.
</string> </string>
<string name="sms_invite_rationale"> <string name="sms_invite_rationale">
CrossWords needs permission to send an invitation via Data SMS. CrossWords needs permission to send an invitation via Data SMS.
</string> </string>
<string name="dicts_storage_rationale"> <string name="dicts_storage_rationale">
CrossWords can store and read wordlists in your device\'s CrossWords can store and read wordlists in your devices
Downloads area but it needs permission to access them Downloads area but it needs permission to access them
there.\n\nYou can safely deny this permission if you will never there.\n\nYou can safely deny this permission if you will never
download wordlists except from inside CrossWords and have not download wordlists except from inside CrossWords and have not
previously stored any there. previously stored any there.
</string> </string>
<string name="phone_state_rationale"> <string name="phone_state_rationale">
Some phones can exchange SMS \"data\" messages. CrossWords would Some phones can exchange SMS “data” messages. CrossWords would
like to offer you this option but needs to ask your device about like to offer you this option but needs to ask your device about
itself first (to learn if it\'s a phone and if so what itself first (to learn if its a phone and if so what
type.)\n\nIf your device can\'t send data SMS (e.g. because it type.)\n\nIf your device cant send data SMS (e.g. because it
isn\'t a phone) or you don\'t ever want to play via SMS isnt a phone) or you dont ever want to play via SMS
(e.g. because you pay for each message), it\'s safe to permanently (e.g. because you pay for each message), its safe to permanently
deny permission. deny permission.
</string> </string>
<string name="phone_lookup_rationale"> <string name="phone_lookup_rationale">
@ -2416,8 +2421,8 @@
Data SMS.</string> Data SMS.</string>
<string name="contacts_rationale"> <string name="contacts_rationale">
CrossWords wants access to your contacts in order to put a name to CrossWords wants access to your contacts in order to put a name to
phone numbers that send you invitations via Data SMS. You\'ll phone numbers that send you invitations via Data SMS. Youll
still be able to receive invitations if you don\'t grant this still be able to receive invitations if you dont grant this
permission, but only the phone number of the sender will be permission, but only the phone number of the sender will be
displayed. displayed.
</string> </string>
@ -2440,14 +2445,14 @@
<string name="gameevent_channel_expl">In-game events</string> <string name="gameevent_channel_expl">In-game events</string>
<!-- Notification that the OS isn't scheduling background services --> <!-- Notification that the OS isn't scheduling background services -->
<string name="servicestall_channel_expl">Stalled messaging alerts</string> <string name="servicestall_channel_expl">Stalled messaging alerts</string>
<string name="not_again_emptybtscan">If a scan doesn\'t find the device you expect:\n <string name="not_again_emptybtscan">If a scan doesnt find the device you expect:\n
• First, just Rescan\n • First, just Rescan\n
• Make sure Bluetooth is enabled on the other device\n • Make sure Bluetooth is enabled on the other device\n
• Launch CrossWords on the other device\n • Launch CrossWords on the other device\n
• If all else fails, reboot this device\n • If all else fails, reboot this device\n
</string> </string>
<string name="title_enable_stallnotify">Show stalled network notification</string> <string name="title_enable_stallnotify">Show stalled network notification</string>
<string name="summary_enable_stallnotify">Notify when Android\'s <string name="summary_enable_stallnotify">Notify when Androids
slow to process outgoing invitations and moves</string> slow to process outgoing invitations and moves</string>
<string name="notify_stall_title">Message sending is stalled</string> <string name="notify_stall_title">Message sending is stalled</string>
<string name="notify_stall_body_fmt">%1$s (and others?) could not send outbound messages <string name="notify_stall_body_fmt">%1$s (and others?) could not send outbound messages
@ -2460,17 +2465,19 @@
<string name="button_more_info">Read more</string> <string name="button_more_info">Read more</string>
<string name="banned_nbs_perms">This game is set up to communicate via data SMS, but apps from the Google Play Store are no longer allowed to do so (with rare exceptions). You can still open the game, but it may not be able to send or receive moves. <string name="banned_nbs_perms">This game is set up to communicate via data SMS, but apps from the Google Play Store are no longer allowed to do so (with rare exceptions). You can still open the game, but it may not be able to send or receive moves.
\n \n
\nYou can remove the data SMS communication setting, or use the \"Read more\" button to check progress on the development of an alternative.</string> \nYou can remove the data SMS communication setting, or use the “Read more” button to check progress on the development of an alternative.</string>
<string name="relay_poll_title">Check for moves how?</string> <string name="relay_poll_title">Check for moves how?</string>
<string name="relay_poll_name_no_fcm">Poll only: no FCM messages</string> <string name="relay_poll_name_no_fcm">Poll only: no FCM messages</string>
<string name="relay_poll_name_no_polling">FCM only: no polling</string> <string name="relay_poll_name_no_polling">FCM only: no polling</string>
<string name="relay_poll_name_both">Default: mix polling and FCM</string> <string name="relay_poll_name_both">Default: mix polling and FCM</string>
<string name="not_again_longtap_lookup">You can look up words BEFORE <string name="not_again_longtap_lookup">You can look up words BEFORE
they\'re committed as moves -- by long-tapping, same as committed theyre committed as moves—by long-tapping, same as committed
words.\n\nUse this feature to check the validity of words you\'re words.\n\nUse this feature to check the validity of words youre
thinking of playing, or to look up an unfamiliar word provided as a thinking of playing, or to look up an unfamiliar word provided as a
hint.</string> hint.</string>
<string name="servicedesc">For transmitting CrossWords moves</string> <string name="servicedesc">For transmitting CrossWords moves</string>
<!-- New strings for duplicate mode play --> <!-- New strings for duplicate mode play -->
<!-- Checkbox in game config dialog --> <!-- Checkbox in game config dialog -->
<string name="duplicate_check">Duplicate mode</string> <string name="duplicate_check">Duplicate mode</string>
@ -2478,16 +2485,19 @@
<string name="dup_tag">Dup</string> <string name="dup_tag">Dup</string>
<!-- Mark game title; will produce something like "Kati vs. Eric (dup.)" --> <!-- Mark game title; will produce something like "Kati vs. Eric (dup.)" -->
<string name="dupe_title_fmt">%1$s (dup.)</string> <string name="dupe_title_fmt">%1$s (dup.)</string>
<!-- Used instead of str_commit_confirm when in duplicate mode --> <!-- Used instead of str_commit_confirm when in duplicate mode -->
<string name="str_submit_confirm">Submit the current move?\n</string> <string name="str_submit_confirm">Submit the current move?\n</string>
<!-- In new-game preferences, do we default to dup-mode being on or off? --> <!-- In new-game preferences, do we default to dup-mode being on or off? -->
<string name="offerdupmode_title">Play in \"duplicate-mode\"</string> <string name="offerdupmode_title">Play in “duplicate-mode”</string>
<string name="offerdupmode_sum">(Experimental!!!) Style of play <string name="offerdupmode_sum">(Experimental!!!) Style of play
where all players have the same tiles</string> where all players have the same tiles</string>
<!-- These are temporary: debug-only preferences to unhide duplicate <!-- These are temporary: debug-only preferences to unhide duplicate
mode. Will go away once it's no longer experimental. --> mode. Will go away once it's no longer experimental. -->
<string name="unhide_dupmode_title">Unhide duplicate-mode options</string> <string name="unhide_dupmode_title">Unhide duplicate-mode options</string>
<string name="unhide_dupmode_summary">(It\'s too experimental right now)</string> <string name="unhide_dupmode_summary">(Its too experimental right now)</string>
<!-- dup mode: tile assignment --> <!-- dup mode: tile assignment -->
<string name="lmi_tiles_dup">Same tiles assigned to all players</string> <string name="lmi_tiles_dup">Same tiles assigned to all players</string>
<!-- dup mode: A "pass" is when nobody can score --> <!-- dup mode: A "pass" is when nobody can score -->
@ -2542,13 +2552,13 @@
<!-- Button in pause confirm/message edit dialog --> <!-- Button in pause confirm/message edit dialog -->
<string name="pause_forget_msg">Forget message</string> <string name="pause_forget_msg">Forget message</string>
<!-- Hint in message edit when pausing --> <!-- Hint in message edit when pausing -->
<string name="pause_expl_hint">Why I\'m doing this</string> <string name="pause_expl_hint">Why Im doing this</string>
<string name="pause_notify_fmt">Player %1$s has paused this game.</string> <string name="pause_notify_fmt">Player %1$s has paused this game.</string>
<string name="unpause_notify_fmt">Player %1$s has un-paused this game.</string> <string name="unpause_notify_fmt">Player %1$s has un-paused this game.</string>
<string name="pause_notify_expl_fmt">Player %1$s has paused this game, with this explanation: %2$s</string> <string name="pause_notify_expl_fmt">Player %1$s has paused this game, with this explanation: %2$s</string>
<string name="unpause_notify_expl_fmt">Player %1$s has un-paused <string name="unpause_notify_expl_fmt">Player %1$s has un-paused
this game, with this explanation: %2$s</string> this game, with this explanation: %2$s</string>
<string name="autopause_expl_fmt">The timer for game \"%1$s\" expired without <string name="autopause_expl_fmt">The timer for game “%1$s” expired without
any moves being made, so it has been paused for you.</string> any moves being made, so it has been paused for you.</string>
<string name="history_unpause_fmt">Unpaused after %2$s by: %1$s.</string> <string name="history_unpause_fmt">Unpaused after %2$s by: %1$s.</string>
<string name="history_pause_fmt">Paused by: %1$s.</string> <string name="history_pause_fmt">Paused by: %1$s.</string>
@ -2583,12 +2593,12 @@
browser. Displayed below are the possible browser. Displayed below are the possible
spellings. Currently this can only happen with Hungarian spellings. Currently this can only happen with Hungarian
wordlists. --> wordlists. -->
<string name="pick_tiles_title">Pick a tile \"spelling\"</string> <string name="pick_tiles_title">Pick a tile “spelling”</string>
<!-- Shown when user types a string that contains non-tiles, <!-- Shown when user types a string that contains non-tiles,
e.g. a Q alone when the langauge has only QU. First e.g. a Q alone when the langauge has only QU. First
substituted is user's string; second substitution is name of substituted is user's string; second substitution is name of
language --> language -->
<string name="no_tiles_exist">\"%1$s\" cannot be spelled with %2$s tiles.</string> <string name="no_tiles_exist">“%1$s” cannot be spelled with %2$s tiles.</string>
<string name="new_feature_title">New Feature Alert</string> <string name="new_feature_title">New Feature Alert</string>
@ -2597,6 +2607,6 @@
<!-- MQTT stuff. May not see the light of day --> <!-- MQTT stuff. May not see the light of day -->
<string name="invite_choice_mqtt">Internet/MQTT</string> <string name="invite_choice_mqtt">Internet/MQTT</string>
<string name="mqtt_invite_title">MQTT Invitation</string> <string name="mqtt_invite_title">MQTT Invitation</string>
<string name="not_again_comms_mqtt">I\'m experimenting with this <string name="not_again_comms_mqtt">Im experimenting with this
as a replacement for the relay.</string> as a replacement for the relay.</string>
</resources> </resources>

View file

@ -120,10 +120,12 @@
</style> </style>
<style name="pat_table_edit" parent="edit_w_clear"> <style name="pat_table_edit" parent="edit_w_clear">
<item name="android:layout_width">200dp</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:maxLines">1</item> <item name="android:maxLines">1</item>
<item name="android:maxLength">15</item>
<item name="android:inputType">text</item> <item name="android:inputType">text</item>
<item name="android:layout_weight">1</item>
</style> </style>
<style name="pat_table_check"> <style name="pat_table_check">
@ -152,6 +154,7 @@
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item> <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
<item name="android:maxLines">1</item>
</style> </style>
<style name="config_spinner_spinner"> <style name="config_spinner_spinner">

View file

@ -2656,34 +2656,32 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1init
DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr;
if ( !!dict ) { if ( !!dict ) {
DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) );
data->globalState = globalState;
data->dict = dict_ref( dict, env );
data->depth = 2;
#ifdef DEBUG
data->guard = GI_GUARD;
#endif
PatDesc patDescs[3]; PatDesc patDescs[3];
XP_MEMSET( patDescs, 0, VSIZE(patDescs) * sizeof(patDescs[0]) ); XP_MEMSET( patDescs, 0, VSIZE(patDescs) * sizeof(patDescs[0]) );
int len = 0; int len = 0;
bool formatOK = true;
if ( !!jPatsArr ) { if ( !!jPatsArr ) {
len = (*env)->GetArrayLength( env, jPatsArr ); len = (*env)->GetArrayLength( env, jPatsArr );
XP_ASSERT( len == 3 ); XP_ASSERT( len == 3 );
for ( int ii = 0; ii < len ; ++ii ) { for ( int ii = 0; formatOK && ii < len ; ++ii ) {
jobject jdesc = (*env)->GetObjectArrayElement( env, jPatsArr, ii ); jobject jdesc = (*env)->GetObjectArrayElement( env, jPatsArr, ii );
if ( !!jdesc ) { if ( !!jdesc ) {
jbyteArray jtiles; jbyteArray jtiles;
if ( getObject( env, jdesc, "tilePat", "[B", &jtiles ) ) { if ( getObject( env, jdesc, "tilePat", "[B", &jtiles ) ) {
int nTiles = (*env)->GetArrayLength( env, jtiles ); int nTiles = (*env)->GetArrayLength( env, jtiles );
if ( 0 < nTiles ) { if ( 0 < nTiles ) {
patDescs[ii].nTiles = nTiles; PatDesc* pd = &patDescs[ii];
jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL ); /* If user adds too many tiles, we'll see it here */
XP_MEMCPY( &patDescs[ii].tiles[0], tiles, if ( nTiles <= VSIZE(pd->tiles) ) {
nTiles * sizeof(patDescs[ii].tiles[0]) ); pd->nTiles = nTiles;
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 ); jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL );
patDescs[ii].anyOrderOk = getBool( env, jdesc, "anyOrderOk" ); XP_MEMCPY( &pd->tiles[0], tiles, nTiles * sizeof(pd->tiles[0]) );
(*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 );
pd->anyOrderOk = getBool( env, jdesc, "anyOrderOk" );
} else {
formatOK = false;
}
} }
deleteLocalRef( env, jtiles ); deleteLocalRef( env, jtiles );
} }
@ -2692,14 +2690,27 @@ Java_org_eehouse_android_xw4_jni_XwJNI_di_1init
} }
} }
DIMinMax mm = { .min = minLen, .max = maxLen }; DictIter* iter = NULL;
data->iter = di_makeIter( data->dict, env, &mm, NULL, 0, if ( formatOK ) {
!!jPatsArr ? patDescs : NULL, VSIZE(patDescs) ); DIMinMax mm = { .min = minLen, .max = maxLen };
iter = di_makeIter( dict, env, &mm, NULL, 0,
!!jPatsArr ? patDescs : NULL, VSIZE(patDescs) );
}
makeIndex( data ); if ( !!iter ) {
(void)di_firstWord( data->iter ); DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) );
data->iter = iter;
data->globalState = globalState;
data->dict = dict_ref( dict, env );
data->depth = 2;
#ifdef DEBUG
data->guard = GI_GUARD;
#endif
makeIndex( data );
(void)di_firstWord( data->iter );
closure = (jlong)data; closure = (jlong)data;
}
} }
return closure; return closure;
} }

View file

@ -145,6 +145,27 @@ typedef enum { PatErrNone,
PatErrDupInSet, PatErrDupInSet,
} PatErr; } PatErr;
#ifdef DEBUG
static const XP_UCHAR*
patErrToStr( PatErr err )
{
const XP_UCHAR* result = NULL;
# define CASESTR(s) case s: result = #s; break
switch ( err ) {
CASESTR(PatErrNone);
CASESTR(PatErrMissingClose);
CASESTR(PatErrMultipleSpellings);
CASESTR(PatErrBadCountTerm);
CASESTR(PatErrNoDigit);
CASESTR(PatErrTooComplex);
CASESTR(PatErrBogusTiles);
CASESTR(PatErrDupInSet);
}
# undef CASESTR
return result;
}
#endif
typedef struct _ParseState { typedef struct _ParseState {
const DictionaryCtxt* dict; const DictionaryCtxt* dict;
const XP_UCHAR* pat; const XP_UCHAR* pat;
@ -263,7 +284,19 @@ onFoundTiles( void* closure, const Tile* tiles, int len )
} }
return 1 == len && PatErrNone == data->err; return 1 == len && PatErrNone == data->err;
} }
static PatErr
addElem( ParseState* ps, PatElem* elem )
{
PatErr err = PatErrNone;
if ( ps->elemIndex < VSIZE(ps->elems) ) {
ps->elems[ps->elemIndex++] = *elem;
} else {
err = PatErrTooComplex;
}
return err;
}
static PatErr static PatErr
parseTile( ParseState* ps ) parseTile( ParseState* ps )
{ {
@ -390,9 +423,10 @@ compileParent( ParseState* ps )
return err; return err;
} }
static void static PatErr
initPS( ParseState* ps, const DictionaryCtxt* dict ) initPS( ParseState* ps, const DictionaryCtxt* dict )
{ {
PatErr result = PatErrNone;
XP_MEMSET( ps, 0, sizeof(*ps) ); XP_MEMSET( ps, 0, sizeof(*ps) );
XP_ASSERT( !!dict ); XP_ASSERT( !!dict );
ps->dict = dict; ps->dict = dict;
@ -407,8 +441,9 @@ initPS( ParseState* ps, const DictionaryCtxt* dict )
#ifdef WITH_START #ifdef WITH_START
PatElem start = { .typ = START, }; PatElem start = { .typ = START, };
ps->elems[ps->elemIndex++] = start; result = addElem( ps, &start );
#endif #endif
return result;
} }
static XP_Bool static XP_Bool
@ -416,19 +451,13 @@ compilePat( ParseState* ps, const XP_UCHAR* strPat )
{ {
ps->pat = strPat; ps->pat = strPat;
ps->patIndex = 0; ps->patIndex = 0;
// XP_ASSERT( !!iter->dict );
/* ParseState ps = { .dict = iter->dict, */
/* .pat = strPat, */
/* .blankMask = ((TileSet)1) << dict_getBlankTile( iter->dict ), */
/* }; */
PatErr err = compileParent( ps ); PatErr err = compileParent( ps );
XP_Bool success = err == PatErrNone && 0 < ps->elemIndex; XP_Bool success = err == PatErrNone && 0 < ps->elemIndex;
/* if ( success ) { */ if ( !success ) {
/* XP_ASSERT( ps.elemIndex < VSIZE(ps.elems) ); */ XP_LOGFF( "=> %s", patErrToStr(err) );
/* replacePat( iter, &ps ); */ }
/* } */
return success; return success;
} }
@ -1310,34 +1339,40 @@ firstWord( DictIter* iter, XP_Bool log )
return success; return success;
} }
static void static XP_Bool
addTilePats( ParseState* ps, const PatDesc* pd ) addTilePats( ParseState* ps, const PatDesc* pd )
{ {
XP_Bool success = XP_TRUE;
XP_Bool anyOrderOk = pd->anyOrderOk; XP_Bool anyOrderOk = pd->anyOrderOk;
PatElem elem = { .typ = CHILD, PatElem elem = { .typ = CHILD,
.minMatched = 1, .minMatched = 1,
.maxMatched = 1, .maxMatched = 1,
}; };
for ( int ii = 0; ii < pd->nTiles; ++ii ) { for ( int ii = 0; success && ii < pd->nTiles; ++ii ) {
#ifdef MULTI_SET #ifdef MULTI_SET
++elem.u.child.tiles.cnts[pd->tiles[ii]]; ++elem.u.child.tiles.cnts[pd->tiles[ii]];
#else #else
elem.u.child.tiles |= 1 << pd->tiles[ii]; elem.u.child.tiles |= 1 << pd->tiles[ii];
#endif #endif
if ( !anyOrderOk ) { if ( !anyOrderOk ) {
ps->elems[ps->elemIndex++] = elem; success = ps->elemIndex < VSIZE(ps->elems);
if ( success ) {
success = PatErrNone == addElem( ps, &elem );
#ifdef MULTI_SET #ifdef MULTI_SET
XP_MEMSET( &elem.u.child.tiles, 0, sizeof(elem.u.child.tiles) ); XP_MEMSET( &elem.u.child.tiles, 0, sizeof(elem.u.child.tiles) );
#else #else
elem.u.child.tiles = 0; elem.u.child.tiles = 0;
#endif #endif
}
} }
} }
if ( anyOrderOk ) { if ( anyOrderOk ) {
elem.u.child.flags |= FLAG_SINGLE; elem.u.child.flags |= FLAG_SINGLE;
elem.minMatched = elem.maxMatched = pd->nTiles; elem.minMatched = elem.maxMatched = pd->nTiles;
ps->elems[ps->elemIndex++] = elem; success = PatErrNone == addElem( ps, &elem );
} }
LOG_RETURNF( "%s", boolToStr(success) );
return success;
} }
static void static void
@ -1352,7 +1387,12 @@ addWildcard( ParseState* ps )
#else #else
elem.u.child.tiles = ps->blankMask; elem.u.child.tiles = ps->blankMask;
#endif #endif
ps->elems[ps->elemIndex++] = elem;
#ifdef DEBUG
PatErr err =
#endif
addElem( ps, &elem );
XP_ASSERT( err == PatErrNone );
} }
static void static void
@ -1405,11 +1445,13 @@ di_makeIter( const DictionaryCtxt* dict, XWEnv xwe, const DIMinMax* minmax,
if ( ii != STARTS_WITH ) { if ( ii != STARTS_WITH ) {
addWildcard( &ps ); addWildcard( &ps );
} }
addTilePats( &ps, ta ); success = addTilePats( &ps, ta );
if ( ii != ENDS_WITH ) { if ( success ) {
addWildcard( &ps ); if ( ii != ENDS_WITH ) {
addWildcard( &ps );
}
copyParsedPat( dict, &pats[nUsed++], &ps, NULL );
} }
copyParsedPat( dict, &pats[nUsed++], &ps, NULL );
} }
} }
} }

View file

@ -77,7 +77,7 @@ typedef struct DictIter DictIter;
* ends-with. The first is more powerful but I'm not sure it'll ever be part * ends-with. The first is more powerful but I'm not sure it'll ever be part
* of a shipping UI.*/ * of a shipping UI.*/
typedef struct _PatDesc { typedef struct _PatDesc {
Tile tiles[16]; Tile tiles[MAX_COLS_DICT];
XP_U16 nTiles; XP_U16 nTiles;
XP_Bool anyOrderOk; XP_Bool anyOrderOk;
} PatDesc; } PatDesc;

View file

@ -1948,7 +1948,6 @@ tmp_noop_sigintterm( int XP_UNUSED(sig) )
exit(0); exit(0);
} }
typedef struct _FTD { typedef struct _FTD {
PatDesc* desc; PatDesc* desc;
XP_Bool called; XP_Bool called;
@ -1960,9 +1959,7 @@ onFoundTiles2( void* closure, const Tile* tiles, int nTiles )
FTD* data = (FTD*)closure; FTD* data = (FTD*)closure;
if ( data->called ) { if ( data->called ) {
XP_LOGFF( "ERROR: called more than once; Hungarian case???" ); XP_LOGFF( "ERROR: called more than once; Hungarian case???" );
} else if ( nTiles > VSIZE(data->desc->tiles) ) { } else if ( nTiles <= VSIZE(data->desc->tiles) ) {
XP_ASSERT(0);
} else {
data->called = XP_TRUE; data->called = XP_TRUE;
data->desc->nTiles = nTiles; data->desc->nTiles = nTiles;
XP_MEMCPY( &data->desc->tiles[0], tiles, nTiles * sizeof(tiles[0]) ); XP_MEMCPY( &data->desc->tiles[0], tiles, nTiles * sizeof(tiles[0]) );
@ -2015,6 +2012,9 @@ patsParamsToIter( const LaunchParams* params, const DictionaryCtxt* dict )
DictIter* iter = di_makeIter( dict, NULL_XWE, dimmp, strPats, nStrPats, DictIter* iter = di_makeIter( dict, NULL_XWE, dimmp, strPats, nStrPats,
0 == nPatDescs ? NULL : descs, nPatDescs ); 0 == nPatDescs ? NULL : descs, nPatDescs );
if ( !iter ) {
XP_LOGFF( "Unable to build iter" );
}
return iter; return iter;
} }
@ -2025,37 +2025,39 @@ testGetNthWord( const LaunchParams* params, const DictionaryCtxt* dict,
const IndexData* data ) const IndexData* data )
{ {
DictIter* iter = patsParamsToIter( params, dict ); DictIter* iter = patsParamsToIter( params, dict );
XP_U32 half = di_countWords( iter, NULL ) / 2; if ( !!iter ) {
XP_U32 interval = half / 100; XP_U32 half = di_countWords( iter, NULL ) / 2;
const XP_UCHAR* delim = params->dumpDelim; /* NULL is ok */ XP_U32 interval = half / 100;
if ( interval == 0 ) { const XP_UCHAR* delim = params->dumpDelim; /* NULL is ok */
++interval; if ( interval == 0 ) {
} ++interval;
}
XP_UCHAR buf[64]; XP_UCHAR buf[64];
int ii, jj; int ii, jj;
for ( ii = 0, jj = half; ii < half; ii += interval, jj += interval ) { for ( ii = 0, jj = half; ii < half; ii += interval, jj += interval ) {
if ( di_getNthWord( iter, NULL_XWE, ii, depth, data ) ) { if ( di_getNthWord( iter, NULL_XWE, ii, depth, data ) ) {
XP_UCHAR buf[64]; XP_UCHAR buf[64];
di_wordToString( iter, buf, VSIZE(buf), delim ); di_wordToString( iter, buf, VSIZE(buf), delim );
XP_ASSERT( 0 == strcmp( buf, words[ii] ) ); XP_ASSERT( 0 == strcmp( buf, words[ii] ) );
# ifdef PRINT_ALL # ifdef PRINT_ALL
XP_LOGFF( "word[%d]: %s", ii, buf ); XP_LOGFF( "word[%d]: %s", ii, buf );
# endif # endif
} else { } else {
XP_ASSERT( 0 ); XP_ASSERT( 0 );
} }
if ( di_getNthWord( iter, NULL_XWE, jj, depth, data ) ) { if ( di_getNthWord( iter, NULL_XWE, jj, depth, data ) ) {
di_wordToString( iter, buf, VSIZE(buf), delim ); di_wordToString( iter, buf, VSIZE(buf), delim );
XP_ASSERT( 0 == strcmp( buf, words[jj] ) ); XP_ASSERT( 0 == strcmp( buf, words[jj] ) );
# ifdef PRINT_ALL # ifdef PRINT_ALL
XP_LOGFF( "word[%d]: %s", jj, buf ); XP_LOGFF( "word[%d]: %s", jj, buf );
# endif # endif
} else { } else {
XP_ASSERT( 0 ); XP_ASSERT( 0 );
}
} }
di_freeIter( iter, NULL_XWE );
} }
di_freeIter( iter, NULL_XWE );
} }
typedef struct _FTData { typedef struct _FTData {
@ -2109,124 +2111,126 @@ walk_dict_test( MPFORMAL const LaunchParams* params, const DictionaryCtxt* dict,
{ {
DictIter* iter = patsParamsToIter( params, dict ); DictIter* iter = patsParamsToIter( params, dict );
LengthsArray lens; if ( !!iter ) {
XP_U32 count = di_countWords( iter, &lens ); LengthsArray lens;
XP_U32 count = di_countWords( iter, &lens );
XP_U32 sum = 0; XP_U32 sum = 0;
for ( long ii = 0; ii < VSIZE(lens.lens); ++ii ) { for ( long ii = 0; ii < VSIZE(lens.lens); ++ii ) {
XP_LOGF( "%d words of length %ld", lens.lens[ii], ii ); XP_LOGF( "%d words of length %ld", lens.lens[ii], ii );
sum += lens.lens[ii]; sum += lens.lens[ii];
}
XP_ASSERT( sum == count );
if ( count > 0 ) {
const XP_UCHAR* delim = params->dumpDelim;
XP_Bool gotOne;
long jj;
char** words = g_malloc( count * sizeof(char*) );
XP_ASSERT( !!words );
for ( jj = 0, gotOne = di_firstWord( iter );
gotOne;
gotOne = di_getNextWord( iter ) ) {
XP_ASSERT( di_getPosition( iter ) == jj );
XP_UCHAR buf[64];
di_wordToString( iter, buf, VSIZE(buf), delim );
# ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif
if ( !!words ) {
words[jj] = g_strdup( buf );
}
++jj;
} }
XP_ASSERT( count == jj ); XP_ASSERT( sum == count );
XP_LOGFF( "comparing runs in both directions" ); if ( count > 0 ) {
for ( jj = 0, gotOne = di_lastWord( iter ); const XP_UCHAR* delim = params->dumpDelim;
gotOne; XP_Bool gotOne;
++jj, gotOne = di_getPrevWord( iter ) ) { long jj;
XP_ASSERT( di_getPosition(iter) == count-jj-1 ); char** words = g_malloc( count * sizeof(char*) );
XP_UCHAR buf[64]; XP_ASSERT( !!words );
di_wordToString( iter, buf, VSIZE(buf), delim );
for ( jj = 0, gotOne = di_firstWord( iter );
gotOne;
gotOne = di_getNextWord( iter ) ) {
XP_ASSERT( di_getPosition( iter ) == jj );
XP_UCHAR buf[64];
di_wordToString( iter, buf, VSIZE(buf), delim );
# ifdef PRINT_ALL # ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf ); fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif # endif
if ( !!words ) { if ( !!words ) {
if ( strcmp( buf, words[count-jj-1] ) ) { words[jj] = g_strdup( buf );
fprintf( stderr, "failure at %ld: %s going forward; %s " }
"going backward\n", jj, words[count-jj-1], buf ); ++jj;
break; }
XP_ASSERT( count == jj );
XP_LOGFF( "comparing runs in both directions" );
for ( jj = 0, gotOne = di_lastWord( iter );
gotOne;
++jj, gotOne = di_getPrevWord( iter ) ) {
XP_ASSERT( di_getPosition(iter) == count-jj-1 );
XP_UCHAR buf[64];
di_wordToString( iter, buf, VSIZE(buf), delim );
# ifdef PRINT_ALL
fprintf( stderr, "%.6ld: %s\n", jj, buf );
# endif
if ( !!words ) {
if ( strcmp( buf, words[count-jj-1] ) ) {
fprintf( stderr, "failure at %ld: %s going forward; %s "
"going backward\n", jj, words[count-jj-1], buf );
break;
}
} }
} }
} XP_ASSERT( count == jj );
XP_ASSERT( count == jj ); XP_LOGFF( "FINISHED comparing runs in both directions" );
XP_LOGFF( "FINISHED comparing runs in both directions" );
XP_LOGFF( "testing getNth" ); XP_LOGFF( "testing getNth" );
testGetNthWord( params, dict, words, 0, NULL ); testGetNthWord( params, dict, words, 0, NULL );
XP_LOGFF( "FINISHED testing getNth" ); XP_LOGFF( "FINISHED testing getNth" );
XP_U16 depth = 2; XP_U16 depth = 2;
XP_U16 maxCount = dict_numTileFaces( dict ); XP_U16 maxCount = dict_numTileFaces( dict );
IndexData data; IndexData data;
data.count = maxCount * maxCount; /* squared because depth == 2! */ data.count = maxCount * maxCount; /* squared because depth == 2! */
data.indices = XP_MALLOC( mpool, data.indices = XP_MALLOC( mpool,
data.count * depth * sizeof(data.indices[0]) ); data.count * depth * sizeof(data.indices[0]) );
data.prefixes = XP_MALLOC( mpool, data.prefixes = XP_MALLOC( mpool,
depth * data.count * sizeof(data.prefixes[0]) ); depth * data.count * sizeof(data.prefixes[0]) );
XP_LOGF( "making index..." ); XP_LOGF( "making index..." );
di_makeIndex( iter, depth, &data ); di_makeIndex( iter, depth, &data );
XP_LOGF( "DONE making index (have %d indices)", data.count ); XP_LOGF( "DONE making index (have %d indices)", data.count );
/* Resize 'em in case not all slots filled */ /* Resize 'em in case not all slots filled */
data.indices = XP_REALLOC( mpool, data.indices, data.indices = XP_REALLOC( mpool, data.indices,
data.count * depth * sizeof(*data.indices) ); data.count * depth * sizeof(*data.indices) );
data.prefixes = XP_REALLOC( mpool, data.prefixes, data.prefixes = XP_REALLOC( mpool, data.prefixes,
depth * data.count * sizeof(*data.prefixes) ); depth * data.count * sizeof(*data.prefixes) );
#if 0 #if 0
for ( ii = 0; ii < nIndices; ++ii ) { for ( ii = 0; ii < nIndices; ++ii ) {
if ( !dict_getNthWord( dict, &word, indices[ii] ) ) { if ( !dict_getNthWord( dict, &word, indices[ii] ) ) {
XP_ASSERT( 0 ); XP_ASSERT( 0 );
}
XP_ASSERT( word.index == indices[ii] );
XP_UCHAR buf1[64];
dict_wordToString( dict, &word, buf1, VSIZE(buf1), delim );
XP_UCHAR buf2[64] = {0};
if ( ii > 0 && dict_getNthWord( dict, &word, indices[ii]-1 ) ) {
dict_wordToString( dict, &word, buf2, VSIZE(buf2), delim );
}
char prfx[8];
dict_tilesToString( dict, &prefixes[depth*ii], depth, prfx,
VSIZE(prfx), NULL );
fprintf( stderr, "%d: index: %ld; prefix: %s; word: %s (prev: %s)\n",
ii, indices[ii], prfx, buf1, buf2 );
} }
XP_ASSERT( word.index == indices[ii] );
XP_UCHAR buf1[64];
dict_wordToString( dict, &word, buf1, VSIZE(buf1), delim );
XP_UCHAR buf2[64] = {0};
if ( ii > 0 && dict_getNthWord( dict, &word, indices[ii]-1 ) ) {
dict_wordToString( dict, &word, buf2, VSIZE(buf2), delim );
}
char prfx[8];
dict_tilesToString( dict, &prefixes[depth*ii], depth, prfx,
VSIZE(prfx), NULL );
fprintf( stderr, "%d: index: %ld; prefix: %s; word: %s (prev: %s)\n",
ii, indices[ii], prfx, buf1, buf2 );
}
#endif #endif
XP_LOGFF( "testing getNth WITH INDEXING" ); XP_LOGFF( "testing getNth WITH INDEXING" );
testGetNthWord( params, dict, words, depth, &data ); testGetNthWord( params, dict, words, depth, &data );
XP_LOGFF( "DONE testing getNth WITH INDEXING" ); XP_LOGFF( "DONE testing getNth WITH INDEXING" );
if ( !!testPrefixes ) { if ( !!testPrefixes ) {
int ii; int ii;
guint count = g_slist_length( testPrefixes ); guint count = g_slist_length( testPrefixes );
for ( ii = 0; ii < count; ++ii ) { for ( ii = 0; ii < count; ++ii ) {
gchar* prefix = (gchar*)g_slist_nth_data( testPrefixes, ii ); gchar* prefix = (gchar*)g_slist_nth_data( testPrefixes, ii );
XP_LOGFF( "prefix %d: %s", ii, prefix ); XP_LOGFF( "prefix %d: %s", ii, prefix );
FTData foundTilesData = { .iter = iter, .words = words, FTData foundTilesData = { .iter = iter, .words = words,
.depth = depth, .data = &data, .depth = depth, .data = &data,
.prefix = prefix, }; .prefix = prefix, };
dict_tilesForString( dict, prefix, 0, onFoundTiles, &foundTilesData ); dict_tilesForString( dict, prefix, 0, onFoundTiles, &foundTilesData );
}
} }
XP_FREE( mpool, data.indices );
XP_FREE( mpool, data.prefixes );
} }
XP_FREE( mpool, data.indices ); di_freeIter( iter, NULL_XWE );
XP_FREE( mpool, data.prefixes );
} }
di_freeIter( iter, NULL_XWE ); XP_LOGFF( "done" );
XP_LOGF( "done" );
} }
static void static void
@ -2253,15 +2257,17 @@ static void
dumpDict( const LaunchParams* params, DictionaryCtxt* dict ) dumpDict( const LaunchParams* params, DictionaryCtxt* dict )
{ {
DictIter* iter = patsParamsToIter( params, dict ); DictIter* iter = patsParamsToIter( params, dict );
const XP_UCHAR* delim = params->dumpDelim; /* NULL is ok */ if ( !!iter ) {
for ( XP_Bool result = di_firstWord( iter ); const XP_UCHAR* delim = params->dumpDelim; /* NULL is ok */
result; for ( XP_Bool result = di_firstWord( iter );
result = di_getNextWord( iter ) ) { result;
XP_UCHAR buf[32]; result = di_getNextWord( iter ) ) {
di_wordToString( iter, buf, VSIZE(buf), delim ); XP_UCHAR buf[32];
fprintf( stdout, "%s\n", buf ); di_wordToString( iter, buf, VSIZE(buf), delim );
fprintf( stdout, "%s\n", buf );
}
di_freeIter( iter, NULL_XWE );
} }
di_freeIter( iter, NULL_XWE );
} }
static void static void
@ -2662,10 +2668,12 @@ testOneString( const LaunchParams* params, GSList* testDicts )
params->useMmap ); params->useMmap );
if ( NULL != dict ) { if ( NULL != dict ) {
DictIter* iter = patsParamsToIter( params, dict ); DictIter* iter = patsParamsToIter( params, dict );
if ( ! di_stringMatches( iter, params->iterTestPatStr ) ) { if ( !!iter ) {
result = 1; if ( ! di_stringMatches( iter, params->iterTestPatStr ) ) {
result = 1;
}
di_freeIter( iter, NULL_XWE );
} }
di_freeIter( iter, NULL_XWE );
} }
} }
return result; return result;