Merge branch 'android_branch' into android_translate

This commit is contained in:
Eric House 2020-04-24 13:07:34 -07:00
commit 5786151e76
73 changed files with 1712 additions and 476 deletions

View file

@ -1,6 +1,6 @@
def INITIAL_CLIENT_VERS = 9 def INITIAL_CLIENT_VERS = 9
def VERSION_CODE_BASE = 152 def VERSION_CODE_BASE = 154
def VERSION_NAME = '4.4.156' def VERSION_NAME = '4.4.158'
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
def BUILD_INFO_NAME = "build-info.txt" def BUILD_INFO_NAME = "build-info.txt"
@ -96,7 +96,6 @@ android {
resValue "string", "app_name", "CrossWords" resValue "string", "app_name", "CrossWords"
resValue "string", "nbs_port", "3344" resValue "string", "nbs_port", "3344"
buildConfigField "boolean", "WIDIR_ENABLED", "false" buildConfigField "boolean", "WIDIR_ENABLED", "false"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
buildConfigField "String", "VARIANT_NAME", "\"Google Play Store\"" buildConfigField "String", "VARIANT_NAME", "\"Google Play Store\""
buildConfigField "int", "VARIANT_CODE", "1" buildConfigField "int", "VARIANT_CODE", "1"
buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\"" buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
@ -112,7 +111,6 @@ android {
resValue "string", "app_name", "CrossWords" resValue "string", "app_name", "CrossWords"
resValue "string", "nbs_port", "3344" resValue "string", "nbs_port", "3344"
buildConfigField "boolean", "WIDIR_ENABLED", "false" buildConfigField "boolean", "WIDIR_ENABLED", "false"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
buildConfigField "String", "VARIANT_NAME", "\"F-Droid\"" buildConfigField "String", "VARIANT_NAME", "\"F-Droid\""
buildConfigField "int", "VARIANT_CODE", "2" buildConfigField "int", "VARIANT_CODE", "2"
buildConfigField "boolean", "FOR_FDROID", "true" buildConfigField "boolean", "FOR_FDROID", "true"
@ -129,7 +127,6 @@ android {
resValue "string", "app_name", "CrossDbg" resValue "string", "app_name", "CrossDbg"
resValue "string", "nbs_port", "3345" resValue "string", "nbs_port", "3345"
buildConfigField "boolean", "WIDIR_ENABLED", "true" buildConfigField "boolean", "WIDIR_ENABLED", "true"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug\"" buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug\""
buildConfigField "int", "VARIANT_CODE", "3" buildConfigField "int", "VARIANT_CODE", "3"
buildConfigField "boolean", "REPORT_LOCKS", "true" buildConfigField "boolean", "REPORT_LOCKS", "true"
@ -148,7 +145,6 @@ android {
resValue "string", "app_name", "CrossDbg" resValue "string", "app_name", "CrossDbg"
resValue "string", "nbs_port", "3345" resValue "string", "nbs_port", "3345"
buildConfigField "boolean", "WIDIR_ENABLED", "true" buildConfigField "boolean", "WIDIR_ENABLED", "true"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug NoSMS\"" buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug NoSMS\""
buildConfigField "int", "VARIANT_CODE", "4" buildConfigField "int", "VARIANT_CODE", "4"
buildConfigField "boolean", "REPORT_LOCKS", "true" buildConfigField "boolean", "REPORT_LOCKS", "true"
@ -165,7 +161,6 @@ android {
resValue "string", "app_name", "CrossWords" resValue "string", "app_name", "CrossWords"
resValue "string", "nbs_port", "3344" resValue "string", "nbs_port", "3344"
buildConfigField "boolean", "WIDIR_ENABLED", "false" buildConfigField "boolean", "WIDIR_ENABLED", "false"
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
buildConfigField "String", "VARIANT_NAME", "\"FOSS\"" buildConfigField "String", "VARIANT_NAME", "\"FOSS\""
buildConfigField "int", "VARIANT_CODE", "5" buildConfigField "int", "VARIANT_CODE", "5"
buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\"" buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
@ -230,7 +225,8 @@ android {
debug { debug {
debuggable true debuggable true
resValue "bool", "DEBUG", "true" resValue "bool", "DEBUG", "true"
minifyEnabled true // for testing // Drop this. Takes too long to build
// minifyEnabled true // for testing
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// This doesn't work on marshmallow: duplicate permission error // This doesn't work on marshmallow: duplicate permission error
// applicationIdSuffix ".debug" // applicationIdSuffix ".debug"

View file

@ -55,7 +55,7 @@
<activity android:name="MainActivity" <activity android:name="MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="standard" android:launchMode="singleTask"
> >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View file

@ -13,9 +13,14 @@
</style> </style>
</head> </head>
<body> <body>
<h2>CrossWords 4.4.156 release</h2> <h2>CrossWords 4.4.158 release</h2>
<p>This release fixes a couple of minor UI issues</p> <p>This release fixes a major crash introduced in 155. <em>Thanks
for all the reports and debugging help!</em></p>
<p><em>Note that some games may be have been permanently damaged and
may have to be deleted. I'm sorry to have missed this
one!</em></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
@ -25,10 +30,9 @@
<h3>New with this release</h3> <h3>New with this release</h3>
<ul> <ul>
<li>Fix so game list entries collapse correctly again</li> <li>Fix timing problems when the "Phonies" setting is DISALLOW</li>
<li>Fix so bringing app to front keeps the open game open</li> <li>Clean up "Edit player" dialog</li>
<li>Show name in scoreboard as "Not here yet" until invitee connects</li> <li>Add support for Hungarian wordlists</li>
<li>Include more Norwegian translations</li>
</ul> </ul>
<p>(The full changelog <p>(The full changelog
@ -36,10 +40,7 @@
<h3>Next up</h3> <h3>Next up</h3>
<ul> <ul>
<li>Improve move-via-NFC</li> <li>Fix sometimes allowing same player to move twice</li>
<li>Support duplicate-style play (popular in France)</li>
<li>Improve play-by-data-sms workaround
using <a href="https://github.com/eehouse/nbsproxy">NBSProxy</a></li>
</ul> </ul>
<p>Please let me know <p>Please let me know

View file

@ -32,7 +32,8 @@ public class Assert {
assertTrue(! val); assertTrue(! val);
} }
public static void assertTrue(boolean val) { public static void assertTrue( boolean val )
{
if (! val) { if (! val) {
Log.e( TAG, "firing assert!" ); Log.e( TAG, "firing assert!" );
DbgUtils.printStack( TAG ); DbgUtils.printStack( TAG );

View file

@ -919,8 +919,13 @@ public class BoardCanvas extends Canvas implements DrawCtx {
Drawable arrow = res.getDrawable( resID ); Drawable arrow = res.getDrawable( resID );
if ( !useDark ) { if ( !useDark ) {
Bitmap src = ((BitmapDrawable)arrow).getBitmap(); Bitmap bitmap = Bitmap.createBitmap( arrow.getIntrinsicWidth(),
Bitmap bitmap = src.copy( Bitmap.Config.ARGB_8888, true ); arrow.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888 );
Canvas canvas = new Canvas( bitmap );
arrow.setBounds( 0, 0, canvas.getWidth(), canvas.getHeight() );
arrow.draw( canvas );
for ( int xx = 0; xx < bitmap.getWidth(); ++xx ) { for ( int xx = 0; xx < bitmap.getWidth(); ++xx ) {
for ( int yy = 0; yy < bitmap.getHeight(); ++yy ) { for ( int yy = 0; yy < bitmap.getHeight(); ++yy ) {
if ( BLACK == bitmap.getPixel( xx, yy ) ) { if ( BLACK == bitmap.getPixel( xx, yy ) ) {
@ -928,7 +933,6 @@ public class BoardCanvas extends Canvas implements DrawCtx {
} }
} }
} }
arrow = new BitmapDrawable( bitmap ); arrow = new BitmapDrawable( bitmap );
} }
return arrow; return arrow;

View file

@ -614,7 +614,10 @@ public class BoardDelegate extends DelegateBase
Log.i( TAG, "opening rowid %d", m_rowid ); Log.i( TAG, "opening rowid %d", m_rowid );
m_haveInvited = args.getBoolean( GameUtils.INVITED, false ); m_haveInvited = args.getBoolean( GameUtils.INVITED, false );
m_overNotShown = true; m_overNotShown = true;
} // init
private void getLock()
{
GameLock.getLockThen( m_rowid, 100L, new Handler(), // this doesn't unlock GameLock.getLockThen( m_rowid, 100L, new Handler(), // this doesn't unlock
new GameLock.GotLockProc() { new GameLock.GotLockProc() {
@Override @Override
@ -648,7 +651,7 @@ public class BoardDelegate extends DelegateBase
} }
} }
} ); } );
} // init } // getLock
@Override @Override
protected void onStart() protected void onStart()
@ -670,6 +673,7 @@ public class BoardDelegate extends DelegateBase
doResume( false ); doResume( false );
} else { } else {
m_resumeSkipped = true; m_resumeSkipped = true;
getLock();
} }
} }
@ -688,9 +692,11 @@ public class BoardDelegate extends DelegateBase
@Override @Override
protected void onStop() protected void onStop()
{ {
if ( isFinishing() && null != m_jniThreadRef ) { if ( null != m_jniThreadRef ) {
m_jniThreadRef.release(); m_jniThreadRef.release();
m_jniThreadRef = null; m_jniThreadRef = null;
} else {
Log.d( TAG, "onStop(): m_jniThreadRef already null" );
} }
super.onStop(); super.onStop();
} }
@ -1855,6 +1861,20 @@ public class BoardDelegate extends DelegateBase
} }
} }
@Override
public void informWordBlocked( final String word, final String dict )
{
runOnUiThread( new Runnable() {
@Override
public void run() {
String msg = LocUtils
.getString( m_activity, R.string.word_blocked_by_phony,
word, dict );
makeOkOnlyBuilder( msg ).show();
}
} );
}
@Override @Override
public void playerScoreHeld( int player ) public void playerScoreHeld( int player )
{ {
@ -1868,7 +1888,7 @@ public class BoardDelegate extends DelegateBase
post( new Runnable() { post( new Runnable() {
@Override @Override
public void run() { public void run() {
showToast( text ); makeOkOnlyBuilder( text ).show();
} }
} ); } );
} }

View file

@ -190,7 +190,7 @@ public class DBHelper extends SQLiteOpenHelper {
{ DICTNAME, "TEXT" } { DICTNAME, "TEXT" }
,{ LOC, "UNSIGNED INTEGER(1)" } ,{ LOC, "UNSIGNED INTEGER(1)" }
,{ WORDCOUNTS, "TEXT" } ,{ WORDCOUNTS, "TEXT" }
,{ ITERMIN, "INTEGRE(4)" } ,{ ITERMIN, "INTEGER(4)" }
,{ ITERMAX, "INTEGER(4)" } ,{ ITERMAX, "INTEGER(4)" }
,{ ITERPOS, "INTEGER" } ,{ ITERPOS, "INTEGER" }
,{ ITERTOP, "INTEGER" } ,{ ITERTOP, "INTEGER" }

View file

@ -22,13 +22,15 @@ package org.eehouse.android.xw4;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement; import android.database.sqlite.SQLiteStatement;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat; import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Environment; import android.os.Environment;
import android.text.TextUtils; import android.text.TextUtils;
@ -1104,9 +1106,11 @@ public class DBUtils {
public static byte[] loadGame( Context context, GameLock lock ) public static byte[] loadGame( Context context, GameLock lock )
{ {
byte[] result = null;
long rowid = lock.getRowid(); long rowid = lock.getRowid();
Assert.assertTrue( ROWID_NOTFOUND != rowid ); Assert.assertTrue( ROWID_NOTFOUND != rowid );
byte[] result = getCached( rowid ); if ( Quarantine.safeToOpen( rowid ) ) {
result = getCached( rowid );
if ( null == result ) { if ( null == result ) {
String[] columns = { DBHelper.SNAPSHOT }; String[] columns = { DBHelper.SNAPSHOT };
String selection = String.format( ROW_ID_FMT, rowid ); String selection = String.format( ROW_ID_FMT, rowid );
@ -1123,7 +1127,7 @@ public class DBUtils {
} }
setCached( rowid, result ); setCached( rowid, result );
} }
Assert.assertTrueNR( null != result ); }
return result; return result;
} }
@ -1539,7 +1543,7 @@ public class DBUtils {
} }
s_groupsCache = result; s_groupsCache = result;
} }
Log.d( TAG, "getGroups() => %s", result ); // Log.d( TAG, "getGroups() => %s", result );
return result; return result;
} // getGroups } // getGroups
@ -2563,6 +2567,32 @@ public class DBUtils {
} }
} }
// Copy my .apk to the Downloads directory, from which a user could more
// easily share it with somebody else. Should be blocked for apks
// installed from the Play store since viral distribution isn't allowed,
// but might be helpful in other cases. Need to figure out how to expose
// it, and how to recommend transmissions. E.g. gmail doesn't let me
// attach an .apk even if I rename it.
static void copyApkToDownloads( Context context )
{
try {
String myName = context.getPackageName();
PackageManager pm = context.getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo( myName, 0 );
File srcPath = new File( appInfo.publicSourceDir );
File destPath = Environment
.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS );
destPath = new File( destPath, context.getString(R.string.app_name) + ".apk" );
FileInputStream src = new FileInputStream( srcPath );
FileOutputStream dest = new FileOutputStream( destPath );
copyFileStream( dest, src );
} catch ( Exception ex ) {
Log.e( TAG, "copyApkToDownloads(): got ex: %s", ex );
}
}
private static String getVariantDBName() private static String getVariantDBName()
{ {
return String.format( "%s_%s", DBHelper.getDBName(), return String.format( "%s_%s", DBHelper.getDBName(),

View file

@ -51,6 +51,10 @@ public class DlgDelegate {
DWNLD_LOC_DICT, DWNLD_LOC_DICT,
NEW_GAME_DFLT_NAME, NEW_GAME_DFLT_NAME,
SEND_EMAIL, SEND_EMAIL,
WRITE_LOG_DB,
CLEAR_LOG_DB,
QUARANTINE_CLEAR,
QUARANTINE_DELETE,
// BoardDelegate // BoardDelegate
UNDO_LAST_ACTION, UNDO_LAST_ACTION,

View file

@ -73,7 +73,7 @@ public class DupeModeTimer extends BroadcastReceiver {
public void gameSaved( Context context, long rowid, public void gameSaved( Context context, long rowid,
GameChangeType change ) GameChangeType change )
{ {
Log.d( TAG, "gameSaved(rowid=%d,change=%s) called", rowid, change ); // Log.d( TAG, "gameSaved(rowid=%d,change=%s) called", rowid, change );
switch( change ) { switch( change ) {
case GAME_CHANGED: case GAME_CHANGED:
case GAME_CREATED: case GAME_CREATED:
@ -81,7 +81,7 @@ public class DupeModeTimer extends BroadcastReceiver {
if ( sDirtyVals.containsKey( rowid ) ) { if ( sDirtyVals.containsKey( rowid ) ) {
sQueue.addOne( context, rowid ); sQueue.addOne( context, rowid );
} else { } else {
Log.d( TAG, "skipping; not dirty" ); // Log.d( TAG, "skipping; not dirty" );
} }
} }
break; break;

View file

@ -386,6 +386,16 @@ public class GameUtils {
return loadMakeGame( context, gi, util, tp, stream, lock.getRowid() ); return loadMakeGame( context, gi, util, tp, stream, lock.getRowid() );
} }
private static CurGameInfo giFromStream( Context context, byte[] stream )
{
CurGameInfo gi = null;
if ( null != stream ) {
gi = new CurGameInfo( context );
XwJNI.gi_from_stream( gi, stream );
}
return gi;
}
private static GamePtr loadMakeGame( Context context, CurGameInfo gi, private static GamePtr loadMakeGame( Context context, CurGameInfo gi,
UtilCtxt util, TransportProcs tp, UtilCtxt util, TransportProcs tp,
byte[] stream, long rowid ) byte[] stream, long rowid )
@ -832,22 +842,28 @@ public class GameUtils {
public static String[] dictNames( Context context, GameLock lock ) public static String[] dictNames( Context context, GameLock lock )
{ {
String[] result = null;
byte[] stream = savedGame( context, lock ); byte[] stream = savedGame( context, lock );
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = giFromStream( context, stream );
XwJNI.gi_from_stream( gi, stream ); if ( null != gi ) {
return gi.dictNames(); result = gi.dictNames();
}
return result;
} }
public static String[] dictNames( Context context, long rowid, public static String[] dictNames( Context context, long rowid,
int[] missingLang ) int[] missingLang )
{ {
String[] result = null;
byte[] stream = savedGame( context, rowid ); byte[] stream = savedGame( context, rowid );
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = giFromStream( context, stream );
XwJNI.gi_from_stream( gi, stream ); if ( null != gi ) {
if ( null != missingLang ) { if ( null != missingLang ) {
missingLang[0] = gi.dictLang; missingLang[0] = gi.dictLang;
} }
return gi.dictNames(); result = gi.dictNames();
}
return result;
} }
public static String[] dictNames( Context context, long rowid ) public static String[] dictNames( Context context, long rowid )
@ -863,7 +879,7 @@ public class GameUtils {
public static boolean gameDictsHere( Context context, GameLock lock ) public static boolean gameDictsHere( Context context, GameLock lock )
{ {
String[] gameDicts = dictNames( context, lock ); String[] gameDicts = dictNames( context, lock );
return gameDictsHere( context, null, gameDicts ); return null != gameDicts && gameDictsHere( context, null, gameDicts );
} }
// Return true if all dicts present. Return list of those that // Return true if all dicts present. Return list of those that
@ -873,7 +889,8 @@ public class GameUtils {
int[] missingLang ) int[] missingLang )
{ {
String[] gameDicts = dictNames( context, rowid, missingLang ); String[] gameDicts = dictNames( context, rowid, missingLang );
return gameDictsHere( context, missingNames, gameDicts ); return null != gameDicts
&& gameDictsHere( context, missingNames, gameDicts );
} }
public static boolean gameDictsHere( Context context, public static boolean gameDictsHere( Context context,
@ -1089,11 +1106,17 @@ public class GameUtils {
boolean success; boolean success;
try ( GameLock lock = GameLock.lock( rowid, 300 ) ) { try ( GameLock lock = GameLock.lock( rowid, 300 ) ) {
success = null != lock; success = null != lock;
if ( success ) { if ( !success ) {
DbgUtils.toastNoLock( TAG, context, rowid,
"replaceDicts(): rowid %d",
rowid );
} else {
byte[] stream = savedGame( context, lock ); byte[] stream = savedGame( context, lock );
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = giFromStream( context, stream );
XwJNI.gi_from_stream( gi, stream ); success = null != gi;
if ( !success ) {
Log.e( TAG, "replaceDicts(): unable to load rowid %d", rowid );
} else {
// first time required so dictNames() will work // first time required so dictNames() will work
gi.replaceDicts( context, newDict ); gi.replaceDicts( context, newDict );
@ -1113,10 +1136,7 @@ public class GameUtils {
summarize( context, lock, gamePtr, gi ); summarize( context, lock, gamePtr, gi );
} }
} else { }
DbgUtils.toastNoLock( TAG, context, rowid,
"replaceDicts(): rowid %d",
rowid );
} }
} }
return success; return success;

View file

@ -62,6 +62,7 @@ import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.jni.LastMoveInfo;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@ -574,6 +575,7 @@ public class GamesListDelegate extends ListDelegateBase
// R.id.games_menu_loaddb, // R.id.games_menu_loaddb,
R.id.games_menu_storedb, R.id.games_menu_storedb,
R.id.games_menu_writegit, R.id.games_menu_writegit,
R.id.games_submenu_logs,
}; };
private static final int[] NOSEL_ITEMS = { private static final int[] NOSEL_ITEMS = {
R.id.games_menu_newgroup, R.id.games_menu_newgroup,
@ -1224,6 +1226,27 @@ public class GamesListDelegate extends ListDelegateBase
} ); } );
} }
private void openWithChecks( long rowid, GameSummary summary )
{
if ( ! m_launchedGames.contains( rowid ) ) {
if ( Quarantine.safeToOpen( rowid ) ) {
makeNotAgainBuilder( R.string.not_again_newselect,
R.string.key_notagain_newselect,
Action.OPEN_GAME )
.setParams( rowid, summary )
.show();
} else {
makeConfirmThenBuilder( R.string.unsafe_open_warning,
Action.QUARANTINE_CLEAR )
.setPosButton( R.string.unsafe_open_disregard )
.setActionPair( Action.QUARANTINE_DELETE,
R.string.button_delete )
.setParams( rowid, summary )
.show();
}
}
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// SelectableItem interface // SelectableItem interface
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -1235,13 +1258,7 @@ public class GamesListDelegate extends ListDelegateBase
// an empty room name. // an empty room name.
if ( clicked instanceof GameListItem ) { if ( clicked instanceof GameListItem ) {
long rowid = ((GameListItem)clicked).getRowID(); long rowid = ((GameListItem)clicked).getRowID();
if ( ! m_launchedGames.contains( rowid ) ) { openWithChecks( rowid, summary );
makeNotAgainBuilder( R.string.not_again_newselect,
R.string.key_notagain_newselect,
Action.OPEN_GAME )
.setParams( rowid, summary )
.show();
}
} }
} }
@ -1342,6 +1359,18 @@ public class GamesListDelegate extends ListDelegateBase
case OPEN_GAME: case OPEN_GAME:
doOpenGame( params ); doOpenGame( params );
break; break;
case QUARANTINE_CLEAR:
long rowid = (long)params[0];
Quarantine.clear( rowid );
GameSummary summary = (GameSummary)params[1];
openWithChecks( rowid, summary );
break;
case QUARANTINE_DELETE:
rowid = (long)params[0];
deleteGames( new long[] {rowid}, true );
break;
case CLEAR_SELS: case CLEAR_SELS:
clearSelections(); clearSelections();
break; break;
@ -1380,6 +1409,30 @@ public class GamesListDelegate extends ListDelegateBase
Utils.emailAuthor( m_activity ); Utils.emailAuthor( m_activity );
break; break;
case WRITE_LOG_DB:
final File logLoc = Log.dumpStored();
post( new Runnable() {
@Override
public void run() {
String dumpMsg;
if ( null == logLoc ) {
dumpMsg = LocUtils.getString( m_activity,
R.string.logstore_notdumped );
} else {
dumpMsg = LocUtils
.getString( m_activity, R.string.logstore_dumped_fmt,
logLoc.getPath() );
}
makeOkOnlyBuilder( dumpMsg ).show();
}
} );
break;
case CLEAR_LOG_DB:
int nDumped = Log.clearStored();
Utils.showToast( m_activity, R.string.logstore_cleared_fmt, nDumped );
break;
case ASKED_PHONE_STATE: case ASKED_PHONE_STATE:
rematchWithNameAndPerm( true, params ); rematchWithNameAndPerm( true, params );
break; break;
@ -1664,6 +1717,23 @@ public class GamesListDelegate extends ListDelegateBase
Utils.gitInfoToClip( m_activity ); Utils.gitInfoToClip( m_activity );
break; break;
case R.id.games_menu_enableLogStorage:
Log.setStoreLogs( true );
break;
case R.id.games_menu_disableLogStorage:
Log.setStoreLogs( false );
break;
case R.id.games_menu_clearLogStorage:
makeConfirmThenBuilder( R.string.logstore_clear_confirm,
Action.CLEAR_LOG_DB )
.setPosButton( R.string.loc_item_clear )
.show();
break;
case R.id.games_menu_dumpLogStorage:
Perms23.tryGetPerms( this, Perm.STORAGE, null,
Action.WRITE_LOG_DB );
break;
default: default:
handled = handleSelGamesItem( itemID, selRowIDs ) handled = handleSelGamesItem( itemID, selRowIDs )
|| handleSelGroupsItem( itemID, getSelGroupIDs() ); || handleSelGroupsItem( itemID, getSelGroupIDs() );
@ -1723,6 +1793,7 @@ public class GamesListDelegate extends ListDelegateBase
&& (BuildConfig.DEBUG || XWPrefs.getDebugEnabled( m_activity )); && (BuildConfig.DEBUG || XWPrefs.getDebugEnabled( m_activity ));
} }
Utils.setItemVisible( menu, R.id.games_game_invites, enable ); Utils.setItemVisible( menu, R.id.games_game_invites, enable );
Utils.setItemVisible( menu, R.id.games_game_markbad, enable );
Utils.setItemVisible( menu, R.id.games_game_netstats, isMultiGame ); Utils.setItemVisible( menu, R.id.games_game_netstats, isMultiGame );
enable = !m_launchedGames.contains( rowID ); enable = !m_launchedGames.contains( rowID );
@ -1875,6 +1946,7 @@ public class GamesListDelegate extends ListDelegateBase
} else { } else {
dropSels = true; // will select the new game instead dropSels = true; // will select the new game instead
post( new Runnable() { post( new Runnable() {
@Override
public void run() { public void run() {
Activity self = m_activity; Activity self = m_activity;
byte[] stream = byte[] stream =
@ -1918,6 +1990,11 @@ public class GamesListDelegate extends ListDelegateBase
makeOkOnlyBuilder( msg ).show(); makeOkOnlyBuilder( msg ).show();
break; break;
// DEBUG only
case R.id.games_game_markbad:
Quarantine.recordOpened( selRowIDs[0] );
break;
default: default:
handled = false; handled = false;
} }

View file

@ -65,7 +65,7 @@ public class InviteChoicesAlert extends DlgDelegateAlert {
if ( Utils.deviceSupportsNBS(context) ) { if ( Utils.deviceSupportsNBS(context) ) {
add( items, means, R.string.invite_choice_data_sms, InviteMeans.SMS_DATA ); add( items, means, R.string.invite_choice_data_sms, InviteMeans.SMS_DATA );
} }
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD ) {
add( items, means, R.string.invite_choice_relay, InviteMeans.RELAY ); add( items, means, R.string.invite_choice_relay, InviteMeans.RELAY );
} }
if ( WiDirWrapper.enabled() ) { if ( WiDirWrapper.enabled() ) {

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */ /* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/* /*
* Copyright 2009 - 2017 by Eric House (xwords@eehouse.org). All rights * Copyright 2009 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -20,22 +20,91 @@
package org.eehouse.android.xw4; package org.eehouse.android.xw4;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import android.os.Process;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.util.Formatter; import java.util.Formatter;
public class Log { public class Log {
private static final String TAG = Log.class.getSimpleName();
private static final String PRE_TAG = BuildConfig.FLAVOR + "-"; private static final String PRE_TAG = BuildConfig.FLAVOR + "-";
private static final String KEY_USE_DB = TAG + "/useDB";
private static final boolean LOGGING_ENABLED private static final boolean LOGGING_ENABLED
= BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD; = BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD;
private static final boolean ERROR_LOGGING_ENABLED = true; private static final boolean ERROR_LOGGING_ENABLED = true;
private static final String LOGS_FILE_NAME = BuildConfig.FLAVOR + "_logsDB.txt";
private static final String LOGS_DB_NAME = "xwlogs_db";
private static final String LOGS_TABLE_NAME = "logs";
private static final String COL_ENTRY = "entry";
private static final String COL_THREAD = "tid";
private static final String COL_PID = "pid";
private static final String COL_ROWID = "rowid";
private static final String COL_TAG = "tag";
private static final String COL_LEVEL = "level";
private static final int DB_VERSION = 1;
private static boolean sEnabled = BuildConfig.DEBUG; private static boolean sEnabled = BuildConfig.DEBUG;
private static boolean sUseDB;
private static WeakReference<Context> sContextRef;
private static enum LOG_LEVEL {
INFO,
ERROR,
WARN,
DEBUG,
}
public static void init( Context context )
{
sContextRef = new WeakReference<>( context );
sUseDB = DBUtils.getBoolFor( context, KEY_USE_DB, false );
}
public static void setStoreLogs( boolean enable )
{
Context context = sContextRef.get();
if ( null != context ) {
DBUtils.setBoolFor( context, KEY_USE_DB, enable );
}
sUseDB = enable;
}
public static void enable( boolean newVal ) public static void enable( boolean newVal )
{ {
sEnabled = newVal; sEnabled = newVal;
} }
public static int clearStored()
{
int result = 0;
LogDBHelper helper = initDB();
if ( null != helper ) {
result = helper.clear();
}
return result;
}
public static File dumpStored()
{
File result = null;
LogDBHelper helper = initDB();
if ( null != helper ) {
result = helper.dumpToFile();
}
return result;
}
public static void enable( Context context ) public static void enable( Context context )
{ {
boolean on = LOGGING_ENABLED || boolean on = LOGGING_ENABLED ||
@ -47,35 +116,54 @@ public class Log {
public static void d( String tag, String fmt, Object... args ) public static void d( String tag, String fmt, Object... args )
{ {
if ( sEnabled ) { if ( sEnabled ) {
String str = new Formatter().format( fmt, args ).toString(); dolog( LOG_LEVEL.DEBUG, tag, fmt, args );
android.util.Log.d( PRE_TAG + tag, str );
} }
} }
public static void w( String tag, String fmt, Object... args ) public static void w( String tag, String fmt, Object... args )
{ {
if ( sEnabled ) { if ( sEnabled ) {
String str = new Formatter().format( fmt, args ).toString(); dolog( LOG_LEVEL.WARN, tag, fmt, args );
android.util.Log.w( PRE_TAG + tag, str );
} }
} }
public static void e( String tag, String fmt, Object... args ) public static void e( String tag, String fmt, Object... args )
{ {
if ( ERROR_LOGGING_ENABLED ) { if ( ERROR_LOGGING_ENABLED ) {
String str = new Formatter().format( fmt, args ).toString(); dolog( LOG_LEVEL.ERROR, tag, fmt, args );
android.util.Log.e( PRE_TAG + tag, str );
} }
} }
public static void i( String tag, String fmt, Object... args ) public static void i( String tag, String fmt, Object... args )
{ {
if ( sEnabled ) { if ( sEnabled ) {
String str = new Formatter().format( fmt, args ).toString(); dolog( LOG_LEVEL.INFO, tag, fmt, args );
android.util.Log.i( PRE_TAG + tag, str );
} }
} }
private static void dolog( LOG_LEVEL level, String tag, String fmt, Object[] args )
{
String str = new Formatter().format( fmt, args ).toString();
String fullTag = PRE_TAG + tag;
switch ( level ) {
case DEBUG:
android.util.Log.d( fullTag, str );
break;
case ERROR:
android.util.Log.e( fullTag, str );
break;
case WARN:
android.util.Log.w( fullTag, str );
break;
case INFO:
android.util.Log.e( fullTag, str );
break;
default:
Assert.failDbg();
}
store( level, fullTag, str );
}
public static void ex( String tag, Exception exception ) public static void ex( String tag, Exception exception )
{ {
if ( sEnabled ) { if ( sEnabled ) {
@ -83,4 +171,138 @@ public class Log {
DbgUtils.printStack( tag, exception.getStackTrace() ); DbgUtils.printStack( tag, exception.getStackTrace() );
} }
} }
private static void llog( String fmt, Object... args )
{
String str = new Formatter().format( fmt, args ).toString();
android.util.Log.d( TAG, str );
}
private static LogDBHelper s_dbHelper;
private synchronized static LogDBHelper initDB()
{
if ( null == s_dbHelper ) {
Context context = sContextRef.get();
if ( null != context ) {
s_dbHelper = new LogDBHelper( context );
// force any upgrade
s_dbHelper.getWritableDatabase().close();
}
}
return s_dbHelper;
}
// Called from jni. Keep name and signature in sync with what's in
// passToJava() in andutils.c
public static void store( String tag, String msg )
{
store( LOG_LEVEL.DEBUG, tag, msg );
}
private static void store( LOG_LEVEL level, String tag, String msg )
{
if ( sUseDB ) {
LogDBHelper helper = initDB();
if ( null != helper ) {
helper.store( level, tag, msg );
}
}
}
private static class LogDBHelper extends SQLiteOpenHelper {
private Context mContext;
LogDBHelper( Context context )
{
super( context, LOGS_DB_NAME, null, DB_VERSION );
mContext = context;
}
@Override
public void onCreate( SQLiteDatabase db )
{
String query = "CREATE TABLE " + LOGS_TABLE_NAME + "("
+ COL_ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT"
+ "," + COL_ENTRY + " TEXT"
+ "," + COL_THREAD + " INTEGER"
+ "," + COL_PID + " INTEGER"
+ "," + COL_TAG + " TEXT"
+ "," + COL_LEVEL + " INTEGER(2)"
+ ");";
db.execSQL( query );
}
@Override
@SuppressWarnings("fallthrough")
public void onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion )
{
String msg = String.format("onUpgrade(%s): old: %d; new: %d", db, oldVersion, newVersion );
android.util.Log.i( TAG, msg );
Assert.failDbg();
}
void store( LOG_LEVEL level, String tag, String msg )
{
int tid = Process.myTid();
int pid = Process.myPid();
ContentValues values = new ContentValues();
values.put( COL_ENTRY, msg );
values.put( COL_THREAD, tid );
values.put( COL_PID, pid );
values.put( COL_TAG, tag );
values.put( COL_LEVEL, level.ordinal() );
long res = getWritableDatabase().insert( LOGS_TABLE_NAME, null, values );
}
File dumpToFile()
{
File dir = Environment.getExternalStorageDirectory();
dir = new File( dir, Environment.DIRECTORY_DOWNLOADS );
File db = new File( dir, LOGS_FILE_NAME );
try {
OutputStream os = new FileOutputStream( db );
OutputStreamWriter osw = new OutputStreamWriter(os);
String[] columns = { COL_ENTRY, COL_TAG, COL_THREAD, COL_PID };
String selection = null;
String orderBy = COL_ROWID;
Cursor cursor = getReadableDatabase().query( LOGS_TABLE_NAME, columns,
selection, null, null, null,
orderBy );
llog( "dumpToFile(): got %d results", cursor.getCount() );
int indx0 = cursor.getColumnIndex( columns[0] );
int indx1 = cursor.getColumnIndex( columns[1] );
int indx2 = cursor.getColumnIndex( columns[2] );
int indx3 = cursor.getColumnIndex( columns[3] );
while ( cursor.moveToNext() ) {
String data = cursor.getString(indx0);
String tag = cursor.getString(indx1);
int tid = cursor.getInt(indx2);
int pid = cursor.getInt(indx3);
StringBuilder builder = new StringBuilder()
.append(String.format("% 5d % 5d", pid, tid)).append(":")
.append(tag).append(":")
.append(data).append("\n")
;
osw.write( builder.toString() );
}
osw.close();
} catch ( IOException ioe ) {
llog( "dumpToFile(): ioe: %s", ioe );
db = null;
}
return db;
}
// Return the number of rows
int clear()
{
int result = getWritableDatabase()
.delete( LOGS_TABLE_NAME, "1", null );
return result;
}
}
} }

View file

@ -992,7 +992,7 @@ public class NFCUtils {
mAdapter.disableReaderMode( mActivity ); mAdapter.disableReaderMode( mActivity );
} }
mInReadMode = wantReadMode; mInReadMode = wantReadMode;
Log.d( TAG, "run(): inReadMode now: %b", mInReadMode ); // Log.d( TAG, "run(): inReadMode now: %b", mInReadMode );
// Now sleep. If we aren't going to want to toggle read // Now sleep. If we aren't going to want to toggle read
// mode soon, sleep until interrupted by a state change, // mode soon, sleep until interrupted by a state change,

View file

@ -0,0 +1,145 @@
/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright 2009-2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.eehouse.android.xw4;
import android.content.Context;
import java.util.HashMap;
import java.util.Map;
import java.io.Serializable;
public class Quarantine {
private static final String TAG = Quarantine.class.getSimpleName();
private static final String DATA_KEY = TAG + "/key";
public static boolean safeToOpen( long rowid )
{
int count;
synchronized ( sDataRef ) {
count = get().getFor( rowid );
}
boolean result = count == 0; // Not too strict?
if ( !result ) {
Log.d( TAG, "safeToOpen(%d) => %b (count=%d)", rowid, result, count );
}
return result;
}
public static void clear( long rowid )
{
synchronized ( sDataRef ) {
get().clear( rowid );
store();
}
}
public static void recordOpened( long rowid )
{
synchronized ( sDataRef ) {
get().increment( rowid );
store();
Log.d( TAG, "recordOpened(%d): %s", rowid, sDataRef[0].toString() );
}
}
public static void recordClosed( long rowid )
{
synchronized ( sDataRef ) {
get().decrement( rowid );
store();
Log.d( TAG, "recordClosed(%d): %s", rowid, sDataRef[0].toString() );
}
}
private static class Data implements Serializable {
private HashMap<Long, Integer> mCounts = new HashMap<>();
synchronized void increment( long rowid ) {
if ( ! mCounts.containsKey(rowid) ) {
mCounts.put(rowid, 0);
}
mCounts.put( rowid, mCounts.get(rowid) + 1 );
}
synchronized void decrement( long rowid )
{
Assert.assertTrue( mCounts.containsKey(rowid) );
mCounts.put( rowid, mCounts.get(rowid) - 1 );
Assert.assertTrueNR( mCounts.get(rowid) >= 0 );
}
synchronized int getFor( long rowid )
{
int result = mCounts.containsKey(rowid) ? mCounts.get( rowid ) : 0;
return result;
}
synchronized void clear( long rowid )
{
mCounts.put( rowid, 0 );
}
@Override
synchronized public String toString()
{
StringBuilder sb = new StringBuilder().append("[");
synchronized ( mCounts ) {
for ( long rowid : mCounts.keySet() ) {
int count = mCounts.get(rowid);
sb.append( String.format("{%d: %d}", rowid, count ) );
}
}
return sb.append("]").toString();
}
}
private static Data[] sDataRef = {null};
private static void store()
{
synchronized( sDataRef ) {
DBUtils.setSerializableFor( getContext(), DATA_KEY, sDataRef[0] );
}
}
private static Data get()
{
Data data;
synchronized ( sDataRef ) {
data = sDataRef[0];
if ( null == data ) {
data = (Data)DBUtils.getSerializableFor( getContext(), DATA_KEY );
if ( null == data ) {
data = new Data();
} else {
Log.d( TAG, "loading existing: %s", data );
}
sDataRef[0] = data;
}
}
return data;
}
private static Context getContext()
{
return XWApp.getContext();
}
}

View file

@ -57,6 +57,8 @@ import org.eehouse.android.xw4.DlgDelegate.Action;
public class RelayInviteDelegate extends InviteDelegate { public class RelayInviteDelegate extends InviteDelegate {
private static final String TAG = RelayInviteDelegate.class.getSimpleName(); private static final String TAG = RelayInviteDelegate.class.getSimpleName();
private static final String RECS_KEY = "TAG" + "/recs"; private static final String RECS_KEY = "TAG" + "/recs";
private static final boolean RELAYINVITE_SUPPORTED
= BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD;
private static int[] BUTTONIDS = { private static int[] BUTTONIDS = {
R.id.button_relay_add, R.id.button_relay_add,
@ -77,7 +79,7 @@ public class RelayInviteDelegate extends InviteDelegate {
SentInvitesInfo info, SentInvitesInfo info,
RequestCode requestCode ) RequestCode requestCode )
{ {
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
Intent intent = Intent intent =
InviteDelegate.makeIntent( activity, RelayInviteActivity.class, InviteDelegate.makeIntent( activity, RelayInviteActivity.class,
nMissing, info ); nMissing, info );
@ -94,7 +96,7 @@ public class RelayInviteDelegate extends InviteDelegate {
@Override @Override
protected void init( Bundle savedInstanceState ) protected void init( Bundle savedInstanceState )
{ {
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
super.init( savedInstanceState ); super.init( savedInstanceState );
String msg = getString( R.string.button_invite ); String msg = getString( R.string.button_invite );
@ -137,7 +139,7 @@ public class RelayInviteDelegate extends InviteDelegate {
@Override @Override
protected void onBarButtonClicked( int id ) protected void onBarButtonClicked( int id )
{ {
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
switch( id ) { switch( id ) {
case R.id.button_relay_add: case R.id.button_relay_add:
Utils.notImpl( m_activity ); Utils.notImpl( m_activity );
@ -189,7 +191,7 @@ public class RelayInviteDelegate extends InviteDelegate {
protected Dialog makeDialog( DBAlert alert, Object[] params ) protected Dialog makeDialog( DBAlert alert, Object[] params )
{ {
Dialog dialog = null; Dialog dialog = null;
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
DialogInterface.OnClickListener lstnr; DialogInterface.OnClickListener lstnr;
switch( alert.getDlgID() ) { switch( alert.getDlgID() ) {
case GET_NUMBER: { case GET_NUMBER: {
@ -334,7 +336,7 @@ public class RelayInviteDelegate extends InviteDelegate {
@Override @Override
protected void tryEnable() protected void tryEnable()
{ {
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
super.tryEnable(); super.tryEnable();
Button button = (Button)findViewById( R.id.button_clear ); Button button = (Button)findViewById( R.id.button_clear );
@ -353,7 +355,7 @@ public class RelayInviteDelegate extends InviteDelegate {
public boolean onPosButton( Action action, Object[] params ) public boolean onPosButton( Action action, Object[] params )
{ {
boolean handled = true; boolean handled = true;
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
switch( action ) { switch( action ) {
case CLEAR_ACTION: case CLEAR_ACTION:
clearSelectedImpl(); clearSelectedImpl();
@ -373,7 +375,7 @@ public class RelayInviteDelegate extends InviteDelegate {
public boolean onDismissed( Action action, Object[] params ) public boolean onDismissed( Action action, Object[] params )
{ {
boolean handled = true; boolean handled = true;
if ( BuildConfig.RELAYINVITE_SUPPORTED ) { if ( RELAYINVITE_SUPPORTED ) {
if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) { if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) {
makeConfirmThenBuilder( R.string.warn_unlimited, makeConfirmThenBuilder( R.string.warn_unlimited,
Action.POST_WARNING_ACTION ) Action.POST_WARNING_ACTION )

View file

@ -65,6 +65,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Formatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -174,9 +175,10 @@ public class Utils {
} }
} }
public static void showToast( Context context, int id ) public static void showToast( Context context, int id, Object... args )
{ {
String msg = LocUtils.getString( context, id ); String msg = LocUtils.getString( context, id );
msg = new Formatter().format( msg, args ).toString();
showToast( context, msg ); showToast( context, msg );
} }

View file

@ -71,6 +71,8 @@ public class XWApp extends Application
Assert.assertTrue( s_context == s_context.getApplicationContext() ); Assert.assertTrue( s_context == s_context.getApplicationContext() );
super.onCreate(); super.onCreate();
Log.init( this );
ProcessLifecycleOwner.get().getLifecycle().addObserver(this); ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
android.util.Log.i( TAG, "onCreate(); git_rev=" android.util.Log.i( TAG, "onCreate(); git_rev="

View file

@ -80,6 +80,7 @@ abstract class XWFragment extends Fragment implements Delegator {
protected void onCreate( DelegateBase dlgt, Bundle sis, boolean hasOptionsMenu ) protected void onCreate( DelegateBase dlgt, Bundle sis, boolean hasOptionsMenu )
{ {
Log.d( TAG, "%H/%s.onCreate() called", this, getClass().getSimpleName() );
m_hasOptionsMenu = hasOptionsMenu; m_hasOptionsMenu = hasOptionsMenu;
this.onCreate( dlgt, sis ); this.onCreate( dlgt, sis );
} }
@ -87,7 +88,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onSaveInstanceState( Bundle outState ) public void onSaveInstanceState( Bundle outState )
{ {
Log.d( TAG, "%s.onSaveInstanceState() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onSaveInstanceState() called", this, getClass().getSimpleName() );
Assert.assertNotNull( m_parentName ); Assert.assertNotNull( m_parentName );
outState.putString( PARENT_NAME, m_parentName ); outState.putString( PARENT_NAME, m_parentName );
outState.putInt( COMMIT_ID, m_commitID ); outState.putInt( COMMIT_ID, m_commitID );
@ -97,7 +98,7 @@ abstract class XWFragment extends Fragment implements Delegator {
protected void onCreate( DelegateBase dlgt, Bundle sis ) protected void onCreate( DelegateBase dlgt, Bundle sis )
{ {
Log.d( TAG, "%s.onCreate() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onCreate() called", this, getClass().getSimpleName() );
super.onCreate( sis ); super.onCreate( sis );
if ( null != sis ) { if ( null != sis ) {
m_parentName = sis.getString( PARENT_NAME ); m_parentName = sis.getString( PARENT_NAME );
@ -122,7 +123,7 @@ abstract class XWFragment extends Fragment implements Delegator {
public View onCreateView( LayoutInflater inflater, ViewGroup container, public View onCreateView( LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState ) Bundle savedInstanceState )
{ {
Log.d( TAG, "%s.onCreateView() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onCreateView() called", this, getClass().getSimpleName() );
sActiveFrags.add(this); sActiveFrags.add(this);
return m_dlgt.inflateView( inflater, container ); return m_dlgt.inflateView( inflater, container );
} }
@ -130,7 +131,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onActivityCreated( Bundle savedInstanceState ) public void onActivityCreated( Bundle savedInstanceState )
{ {
Log.d( TAG, "%s.onActivityCreated() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onActivityCreated() called", this, getClass().getSimpleName() );
m_dlgt.init( savedInstanceState ); m_dlgt.init( savedInstanceState );
super.onActivityCreated( savedInstanceState ); super.onActivityCreated( savedInstanceState );
if ( m_hasOptionsMenu ) { if ( m_hasOptionsMenu ) {
@ -141,7 +142,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onPause() public void onPause()
{ {
Log.d( TAG, "%s.onPause() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onPause() called", this, getClass().getSimpleName() );
m_dlgt.onPause(); m_dlgt.onPause();
super.onPause(); super.onPause();
} }
@ -149,7 +150,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onResume() public void onResume()
{ {
Log.d( TAG, "%s.onResume() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onResume() called", this, getClass().getSimpleName() );
super.onResume(); super.onResume();
m_dlgt.onResume(); m_dlgt.onResume();
} }
@ -157,7 +158,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onStart() public void onStart()
{ {
Log.d( TAG, "%s.onStart() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onStart() called", this, getClass().getSimpleName() );
super.onStart(); super.onStart();
m_dlgt.onStart(); m_dlgt.onStart();
} }
@ -165,7 +166,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onStop() public void onStop()
{ {
Log.d( TAG, "%s.onStop() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onStop() called", this, getClass().getSimpleName() );
m_dlgt.onStop(); m_dlgt.onStop();
super.onStop(); super.onStop();
} }
@ -173,7 +174,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onDestroy() public void onDestroy()
{ {
Log.d( TAG, "%s.onDestroy() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onDestroy() called", this, getClass().getSimpleName() );
m_dlgt.onDestroy(); m_dlgt.onDestroy();
sActiveFrags.remove( this ); sActiveFrags.remove( this );
super.onDestroy(); super.onDestroy();
@ -182,7 +183,7 @@ abstract class XWFragment extends Fragment implements Delegator {
@Override @Override
public void onActivityResult( int requestCode, int resultCode, Intent data ) public void onActivityResult( int requestCode, int resultCode, Intent data )
{ {
Log.d( TAG, "%s.onActivityResult() called", getClass().getSimpleName() ); Log.d( TAG, "%H/%s.onActivityResult() called", this, getClass().getSimpleName() );
m_dlgt.onActivityResult( RequestCode.values()[requestCode], m_dlgt.onActivityResult( RequestCode.values()[requestCode],
resultCode, data ); resultCode, data );
} }

View file

@ -51,7 +51,7 @@ public class CurGameInfo implements Serializable {
private static final String PHONIES = "PHONIES"; private static final String PHONIES = "PHONIES";
private static final String DUP = "DUP"; private static final String DUP = "DUP";
public enum XWPhoniesChoice { PHONIES_IGNORE, PHONIES_WARN, PHONIES_DISALLOW }; public enum XWPhoniesChoice { PHONIES_IGNORE, PHONIES_WARN, PHONIES_DISALLOW, PHONIES_BLOCK, };
public enum DeviceRole { SERVER_STANDALONE, SERVER_ISSERVER, SERVER_ISCLIENT }; public enum DeviceRole { SERVER_STANDALONE, SERVER_ISSERVER, SERVER_ISCLIENT };
public String dictName; public String dictName;

View file

@ -123,9 +123,8 @@ public class DUtilCtxt {
private static final int STRSD_DUP_ONESCORE = 29; private static final int STRSD_DUP_ONESCORE = 29;
private static final int STR_PENDING_PLAYER = 30; private static final int STR_PENDING_PLAYER = 30;
public String getUserString( int stringCode ) public String getUserString( final int stringCode )
{ {
Log.d( TAG, "getUserString(%d)", stringCode );
int id = 0; int id = 0;
switch( stringCode ) { switch( stringCode ) {
case STR_ROBOT_MOVED: case STR_ROBOT_MOVED:
@ -217,7 +216,7 @@ public class DUtilCtxt {
} }
String result = (0 == id) ? "" : LocUtils.getString( m_context, id ); String result = (0 == id) ? "" : LocUtils.getString( m_context, id );
Log.d( TAG, "getUserString() => %s", result ); Log.d( TAG, "getUserString(%d) => %s", stringCode, result );
return result; return result;
} }

View file

@ -197,9 +197,13 @@ public class JNIThread extends Thread implements AutoCloseable {
m_queue.clear(); m_queue.clear();
} }
boolean success = false;
DictUtils.DictPairs pairs = null;
String[] dictNames = GameUtils.dictNames( context, m_lock ); String[] dictNames = GameUtils.dictNames( context, m_lock );
DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames ); if ( null != dictNames ) {
boolean success = !pairs.anyMissing( dictNames ); pairs = DictUtils.openDicts( context, dictNames );
success = !pairs.anyMissing( dictNames );
}
if ( success ) { if ( success ) {
byte[] stream = GameUtils.savedGame( context, m_lock ); byte[] stream = GameUtils.savedGame( context, m_lock );
@ -401,7 +405,7 @@ public class JNIThread extends Thread implements AutoCloseable {
// PENDING: once certain this is true, stop saving the full array and // PENDING: once certain this is true, stop saving the full array and
// instead save the hash. Also, update it after each save. // instead save the hash. Also, update it after each save.
if ( hashesEqual ) { if ( hashesEqual ) {
Log.d( TAG, "save_jni(): no change in game; can skip saving" ); // Log.d( TAG, "save_jni(): no change in game; can skip saving" );
} else { } else {
// Don't need this!!!! this only runs on the run() thread // Don't need this!!!! this only runs on the run() thread
synchronized( this ) { synchronized( this ) {

View file

@ -62,6 +62,7 @@ public interface UtilCtxt {
void remSelected(); void remSelected();
void timerSelected( boolean inDuplicateMode, boolean canPause ); void timerSelected( boolean inDuplicateMode, boolean canPause );
void setIsServer( boolean isServer ); void setIsServer( boolean isServer );
void informWordBlocked( String word, String dict );
void bonusSquareHeld( int bonus ); void bonusSquareHeld( int bonus );
void playerScoreHeld( int player ); void playerScoreHeld( int player );

View file

@ -106,6 +106,12 @@ public class UtilCtxtImpl implements UtilCtxt {
subclassOverride( "setIsServer" ); subclassOverride( "setIsServer" );
} }
@Override
public void informWordBlocked( String word, String dict )
{
subclassOverride( "informWordBlocked" );
}
@Override @Override
public void bonusSquareHeld( int bonus ) public void bonusSquareHeld( int bonus )
{ {

View file

@ -28,6 +28,7 @@ import org.eehouse.android.xw4.Assert;
import org.eehouse.android.xw4.BuildConfig; import org.eehouse.android.xw4.BuildConfig;
import org.eehouse.android.xw4.Log; import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.NetLaunchInfo; import org.eehouse.android.xw4.NetLaunchInfo;
import org.eehouse.android.xw4.Quarantine;
import org.eehouse.android.xw4.Utils; import org.eehouse.android.xw4.Utils;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
@ -46,6 +47,7 @@ public class XwJNI {
m_ptrGame = ptr; m_ptrGame = ptr;
m_rowid = rowid; m_rowid = rowid;
mStack = android.util.Log.getStackTraceString(new Exception()); mStack = android.util.Log.getStackTraceString(new Exception());
Quarantine.recordOpened( rowid );
} }
public synchronized long ptr() public synchronized long ptr()
@ -73,6 +75,7 @@ public class XwJNI {
// getClass().getName(), this, m_rowid, m_refCount ); // getClass().getName(), this, m_rowid, m_refCount );
if ( 0 == m_refCount ) { if ( 0 == m_refCount ) {
if ( 0 != m_ptrGame ) { if ( 0 != m_ptrGame ) {
Quarantine.recordClosed( m_rowid );
if ( haveEnv( getJNI().m_ptrGlobals ) ) { if ( haveEnv( getJNI().m_ptrGlobals ) ) {
game_dispose( this ); // will crash if haveEnv fails game_dispose( this ); // will crash if haveEnv fails
} else { } else {
@ -165,6 +168,7 @@ public class XwJNI {
public static void gi_from_stream( CurGameInfo gi, byte[] stream ) public static void gi_from_stream( CurGameInfo gi, byte[] stream )
{ {
Assert.assertNotNull( stream );
gi_from_stream( getJNI().m_ptrGlobals, gi, stream ); // called here gi_from_stream( getJNI().m_ptrGlobals, gi, stream ); // called here
} }
@ -308,8 +312,10 @@ public class XwJNI {
// int timerHeight ); // int timerHeight );
public static native boolean board_zoom( GamePtr gamePtr, int zoomBy, public static native boolean board_zoom( GamePtr gamePtr, int zoomBy,
boolean[] canZoom ); boolean[] canZoom );
public static native boolean board_getActiveRect( GamePtr gamePtr, Rect rect,
int[] dims ); // Not available if XWFEATURE_ACTIVERECT not #defined in C
// public static native boolean board_getActiveRect( GamePtr gamePtr, Rect rect,
// int[] dims );
public static native boolean board_handlePenDown( GamePtr gamePtr, public static native boolean board_handlePenDown( GamePtr gamePtr,
int xx, int yy, int xx, int yy,

View file

@ -22,7 +22,9 @@
<LinearLayout android:layout_width="fill_parent" <LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:padding="8dp"
>
<CheckBox android:id="@+id/remote_check" <CheckBox android:id="@+id/remote_check"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -36,20 +38,21 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
>
<TextView android:layout_height="wrap_content" <TextView android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:text="@string/player_label" android:text="@string/player_label"
android:gravity="left" android:gravity="left"
android:layout_marginRight="5dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
/> />
<EditText android:id="@+id/player_name_edit" <EditText android:id="@+id/player_name_edit"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:scrollHorizontally="true" android:scrollHorizontally="true"
android:selectAllOnFocus="true" android:selectAllOnFocus="true"
android:gravity="fill_horizontal" android:gravity="fill_horizontal"
@ -58,13 +61,17 @@
android:inputType="textCapWords" android:inputType="textCapWords"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
/> />
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
>
<TextView android:id="@+id/dict_label" <TextView android:id="@+id/dict_label"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:gravity="left" android:gravity="left"
android:layout_marginRight="5dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
/> />
@ -73,6 +80,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawSelectorOnTop="true" android:drawSelectorOnTop="true"
/> />
</LinearLayout>
<CheckBox android:id="@+id/robot_check" <CheckBox android:id="@+id/robot_check"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -83,13 +91,11 @@
<LinearLayout android:id="@+id/password_set" <LinearLayout android:id="@+id/password_set"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:orientation="vertical"> android:orientation="horizontal"
>
<TextView android:layout_height="wrap_content" <TextView android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:text="@string/password_label" android:text="@string/password_label"
android:gravity="left" android:gravity="left"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
@ -98,8 +104,6 @@
<EditText android:id="@+id/password_edit" <EditText android:id="@+id/password_edit"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:scrollHorizontally="true" android:scrollHorizontally="true"
android:gravity="fill_horizontal" android:gravity="fill_horizontal"
android:maxLines="1" android:maxLines="1"

View file

@ -38,5 +38,7 @@
<item android:id="@+id/games_game_invites" <item android:id="@+id/games_game_invites"
android:title="@string/board_menu_game_showInvites" android:title="@string/board_menu_game_showInvites"
/> />
<item android:id="@+id/games_game_markbad"
android:title="@string/list_item_markbad"
/>
</menu> </menu>

View file

@ -122,4 +122,22 @@
android:title="@string/gamel_menu_writegit" android:title="@string/gamel_menu_writegit"
/> />
<item android:id="@+id/games_submenu_logs"
android:title="@string/gamel_menu_logs"
>
<menu>
<item android:id="@+id/games_menu_enableLogStorage"
android:title="@string/gamel_menu_logstore_on"
/>
<item android:id="@+id/games_menu_disableLogStorage"
android:title="@string/gamel_menu_logstore_off"
/>
<item android:id="@+id/games_menu_dumpLogStorage"
android:title="@string/gamel_menu_logstore_dump"
/>
<item android:id="@+id/games_menu_clearLogStorage"
android:title="@string/gamel_menu_logstore_clear"
/>
</menu>
</item>
</menu> </menu>

View file

@ -242,6 +242,7 @@
<item>Czech</item> <item>Czech</item>
<item>Greek</item> <item>Greek</item>
<item>Slovak</item> <item>Slovak</item>
<item>Hungarian</item>
</string-array> </string-array>
<string-array name="language_codes"> <string-array name="language_codes">
@ -265,6 +266,7 @@
<item>cs</item> <item>cs</item>
<item>el</item> <item>el</item>
<item>sk</item> <item>sk</item>
<item>hu</item>
</string-array> </string-array>
<!-- Triples of Name, supported codes, and URL format string --> <!-- Triples of Name, supported codes, and URL format string -->

View file

@ -417,7 +417,7 @@
player will use. The language the game will use (which player will use. The language the game will use (which
constrains the choice of wordlists) is substituted in for constrains the choice of wordlists) is substituted in for
"%1$s". --> "%1$s". -->
<string name="dict_lang_label_fmt">Wordlist (in %1$s)</string> <string name="dict_lang_label_fmt">Wordlist (%1$s)</string>
<!-- If the dropdown is selected, this is the title displayed <!-- If the dropdown is selected, this is the title displayed
above the list of selectable items. The language the game above the list of selectable items. The language the game
will use is substituted in for "%1$s". --> will use is substituted in for "%1$s". -->
@ -425,13 +425,13 @@
<!-- checkbox determining if player is robot/automated or human --> <!-- checkbox determining if player is robot/automated or human -->
<string name="robot_label">Robot player</string> <string name="robot_label">Robot player</string>
<!-- text of label identifying the field where human players can <!-- text of label identifying the field where human players can
enter an option password. The label and field disappear when enter an optional password. The label and field disappear when
the robot player checkbox is checked because it makes no the robot player checkbox is checked because it makes no
sense for a robot to have a password. In fact, passwords sense for a robot to have a password. In fact, passwords
only make sense where there's more than one local human only make sense where there's more than one local human
player on a device, so they are infrequently used in network player on a device, so they are infrequently used in network
games as well. --> games as well. -->
<string name="password_label">Password</string> <string name="password_label">Password:</string>
<!-- <!--
############################################################ ############################################################
# :Screens: # :Screens:
@ -2306,6 +2306,8 @@
<string name="processing_games">Processing games</string> <string name="processing_games">Processing games</string>
<string name="list_item_select">Select</string> <string name="list_item_select">Select</string>
<string name="list_item_deselect">De-select</string> <string name="list_item_deselect">De-select</string>
<!-- Debug-only item for testing corrupt game warnings -->
<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>
@ -2519,4 +2521,30 @@
<string name="history_pause_fmt">Paused by: %1$s.</string> <string name="history_pause_fmt">Paused by: %1$s.</string>
<string name="history_msg_fmt"> Message: %1$s.</string> <string name="history_msg_fmt"> Message: %1$s.</string>
<string name="history_autopause">Auto-paused.</string> <string name="history_autopause">Auto-paused.</string>
<string name="unsafe_open_warning">This game has caused CrossWords
to crash recently and is likely damaged. Opening it might cause
another crash. Do you want to open it anyway?</string>
<string name="unsafe_open_disregard">Open anyway</string>
<string name="word_blocked_by_phony">Word %1$s not found in wordlist %2$s.</string>
<string name="gamel_menu_logs">Debug logs</string>
<!-- Debug-build-only menu to turn on saving of logs -->
<string name="gamel_menu_logstore_on">Enable log storage</string>
<!-- Debug-build-only menu to turn off saving of logs -->
<string name="gamel_menu_logstore_off">Diable log storage</string>
<!-- Debug-build-only menu to clear/delete saved logs -->
<string name="gamel_menu_logstore_clear">Clear stored logs</string>
<!-- Debug-build-only menu to write saved logs to a world-readable file -->
<string name="gamel_menu_logstore_dump">Write stored logs to file</string>
<!-- Debug-build-only status message shown after logs successfully written to file -->
<string name="logstore_dumped_fmt">Logs written to file %1$s</string>
<!-- Debug-build-only status message shown when unable to write logs -->
<string name="logstore_notdumped">Unable to write logs</string>
<!-- Debug-build-only status message shown after logs successfully cleared -->
<string name="logstore_cleared_fmt">%1$d log entries deleted</string>
<!-- Debug-build-only question asked to confirm deletion of saved logs -->
<string name="logstore_clear_confirm">Are you sure you want to
erase your debug logs? This action cannot be undone.</string>
</resources> </resources>

View file

@ -62,18 +62,18 @@
android:defaultValue="true" android:defaultValue="true"
/> />
<CheckBoxPreference android:key="@string/key_init_dupmodeon"
android:title="@string/offerdupmode_title"
android:summary="@string/offerdupmode_sum"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_init_nethintsallowed" <CheckBoxPreference android:key="@string/key_init_nethintsallowed"
android:title="@string/nethints_allowed" android:title="@string/nethints_allowed"
android:summary="@string/nethints_allowed_sum" android:summary="@string/nethints_allowed_sum"
android:defaultValue="true" android:defaultValue="true"
/> />
<CheckBoxPreference android:key="@string/key_init_dupmodeon"
android:title="@string/offerdupmode_title"
android:summary="@string/offerdupmode_sum"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_init_autojuggle" <CheckBoxPreference android:key="@string/key_init_autojuggle"
android:title="@string/init_autojuggle" android:title="@string/init_autojuggle"
android:summary="@string/init_autojuggle_sum" android:summary="@string/init_autojuggle_sum"
@ -401,7 +401,7 @@
<CheckBoxPreference android:key="@string/key_enable_dup_invite" <CheckBoxPreference android:key="@string/key_enable_dup_invite"
android:title="@string/enable_dupes_title" android:title="@string/enable_dupes_title"
android:summary="@string/enable_dupes_summary" android:summary="@string/enable_dupes_summary"
android:defaultValue="false" android:defaultValue="@bool/DEBUG"
/> />
<CheckBoxPreference android:key="@string/key_enable_pending_count" <CheckBoxPreference android:key="@string/key_enable_pending_count"

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation"
>
<!-- These are temporary, until the new feature's released -->
<string name="phonies_block">Block phonies</string>
<string-array name="phony_names">
<item>@string/phonies_ignore</item>
<item>@string/phonies_warn</item>
<item>@string/phonies_disallow</item>
<item>@string/phonies_block</item>
</string-array>
</resources>

View file

@ -0,0 +1 @@
../../../xw4d/res/values/tmp_for_phony.xml

View file

@ -30,6 +30,8 @@
void void
and_assert( const char* test, int line, const char* file, const char* func ) and_assert( const char* test, int line, const char* file, const char* func )
{ {
RAW_LOG( "assertion \"%s\" failed: line %d in %s() in %s",
test, line, func, file );
XP_LOGF( "assertion \"%s\" failed: line %d in %s() in %s", XP_LOGF( "assertion \"%s\" failed: line %d in %s() in %s",
test, line, func, file ); test, line, func, file );
__android_log_assert( test, "ASSERT", "line %d in %s() in %s", __android_log_assert( test, "ASSERT", "line %d in %s() in %s",
@ -423,11 +425,14 @@ setIntInArray( JNIEnv* env, jintArray arr, int index, int val )
jobjectArray jobjectArray
makeStringArray( JNIEnv *env, const int count, const XP_UCHAR** vals ) makeStringArray( JNIEnv *env, const int count, const XP_UCHAR** vals )
{
jobjectArray jarray;
{ {
jclass clas = (*env)->FindClass(env, "java/lang/String"); jclass clas = (*env)->FindClass(env, "java/lang/String");
jstring empty = (*env)->NewStringUTF( env, "" ); jstring empty = (*env)->NewStringUTF( env, "" );
jobjectArray jarray = (*env)->NewObjectArray( env, count, clas, empty ); jarray = (*env)->NewObjectArray( env, count, clas, empty );
deleteLocalRefs( env, clas, empty, DELETE_NO_REF ); deleteLocalRefs( env, clas, empty, DELETE_NO_REF );
}
for ( int ii = 0; !!vals && ii < count; ++ii ) { for ( int ii = 0; !!vals && ii < count; ++ii ) {
jstring jstr = (*env)->NewStringUTF( env, vals[ii] ); jstring jstr = (*env)->NewStringUTF( env, vals[ii] );
@ -763,6 +768,28 @@ deleteLocalRefs( JNIEnv* env, ... )
} }
#ifdef DEBUG #ifdef DEBUG
/* A bunch of threads are generating log statements. */
static void
passToJava( const char* tag, const char* msg )
{
JNIEnv* env = waitEnvFromGlobals();
if ( !!env ) {
jstring jtag = (*env)->NewStringUTF( env, tag );
jstring jbuf = (*env)->NewStringUTF( env, msg );
jclass clazz = (*env)->FindClass( env, PKG_PATH("Log") );
XP_ASSERT( !!clazz );
jmethodID mid = (*env)->GetStaticMethodID( env, clazz, "store",
"(Ljava/lang/String;Ljava/lang/String;)V" );
(*env)->CallStaticVoidMethod( env, clazz, mid, jtag, jbuf );
deleteLocalRefs( env, clazz, jtag, jbuf, DELETE_NO_REF );
releaseEnvFromGlobals( env );
} else {
RAW_LOG( "env is NULL; dropping" );
}
}
static void static void
debugf( const char* format, va_list ap ) debugf( const char* format, va_list ap )
{ {
@ -781,7 +808,7 @@ debugf( const char* format, va_list ap )
vsnprintf( buf + len, sizeof(buf)-len, format, ap ); vsnprintf( buf + len, sizeof(buf)-len, format, ap );
} }
(void)__android_log_write( ANDROID_LOG_DEBUG, const char* tag =
# if defined VARIANT_xw4NoSMS || defined VARIANT_xw4fdroid || defined VARIANT_xw4SMS # if defined VARIANT_xw4NoSMS || defined VARIANT_xw4fdroid || defined VARIANT_xw4SMS
"xw4" "xw4"
# elif defined VARIANT_xw4d || defined VARIANT_xw4dNoSMS # elif defined VARIANT_xw4d || defined VARIANT_xw4dNoSMS
@ -789,7 +816,27 @@ debugf( const char* format, va_list ap )
# elif defined VARIANT_xw4dup || defined VARIANT_xw4dupNoSMS # elif defined VARIANT_xw4dup || defined VARIANT_xw4dupNoSMS
"x4du" "x4du"
# endif # endif
, buf ); ;
(void)__android_log_write( ANDROID_LOG_DEBUG, tag, buf );
passToJava( tag, buf );
}
void
raw_log( const char* func, const char* fmt, ... )
{
char buf[1024];
int len = snprintf( buf, VSIZE(buf) - 1, "in %s(): %s", func, fmt );
buf[len] = '\0';
va_list ap;
va_start( ap, fmt );
char buf2[1024];
len = vsnprintf( buf2, VSIZE(buf2) - 1, buf, ap );
va_end( ap );
(void)__android_log_write( ANDROID_LOG_DEBUG, "raw", buf2 );
} }
void void

View file

@ -108,5 +108,16 @@ XP_U32 getCurSeconds( JNIEnv* env );
void deleteLocalRef( JNIEnv* env, jobject jobj ); void deleteLocalRef( JNIEnv* env, jobject jobj );
void deleteLocalRefs( JNIEnv* env, ... ); void deleteLocalRefs( JNIEnv* env, ... );
JNIEnv* waitEnvFromGlobals();
void releaseEnvFromGlobals( JNIEnv* env );
void raw_log( const char* func, const char* fmt, ... );
#ifdef DEBUG
# define RAW_LOG(...) raw_log( __func__, __VA_ARGS__ )
#else
# define RAW_LOG(...)
#endif
# define DELETE_NO_REF ((jobject)-1) /* terminates above varargs list */ # define DELETE_NO_REF ((jobject)-1) /* terminates above varargs list */
#endif #endif

View file

@ -27,6 +27,7 @@
#include "andutils.h" #include "andutils.h"
#include "paths.h" #include "paths.h"
#include "LocalizedStrIncludes.h" #include "LocalizedStrIncludes.h"
#include "dbgutil.h"
#define MAX_QUANTITY_STRS 4 #define MAX_QUANTITY_STRS 4
@ -705,6 +706,17 @@ and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer )
UTIL_CBK_TAIL(); UTIL_CBK_TAIL();
} }
static void
and_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word, const XP_UCHAR* dict )
{
UTIL_CBK_HEADER( "informWordBlocked", "(Ljava/lang/String;Ljava/lang/String;)V" );
jstring jword = (*env)->NewStringUTF( env, word );
jstring jdict = (*env)->NewStringUTF( env, dict );
(*env)->CallVoidMethod( env, util->jutil, mid, jword, jdict );
deleteLocalRefs( env, jword, DELETE_NO_REF );
UTIL_CBK_TAIL();
}
#ifdef XWFEATURE_DEVID #ifdef XWFEATURE_DEVID
static const XP_UCHAR* static const XP_UCHAR*
and_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ ) and_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ )
@ -907,8 +919,10 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
#endif #endif
SET_PROC(getDevUtilCtxt); SET_PROC(getDevUtilCtxt);
SET_PROC(informWordBlocked);
#undef SET_PROC #undef SET_PROC
assertTableFull( vtable, sizeof(*vtable), "util" );
return (XW_UtilCtxt*)util; return (XW_UtilCtxt*)util;
} /* makeUtil */ } /* makeUtil */
@ -965,6 +979,9 @@ makeDUtil( MPFORMAL EnvThreadInfo* ti, jobject jdutil, VTableMgr* vtMgr,
SET_DPROC(notifyPause); SET_DPROC(notifyPause);
SET_DPROC(onDupTimerChanged); SET_DPROC(onDupTimerChanged);
#undef SET_DPROC
assertTableFull( vtable, sizeof(*vtable), "dutil" );
return &dutil->dutil; return &dutil->dutil;
} }

View file

@ -157,8 +157,8 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller )
found = true; found = true;
if ( env != entry->env ) { if ( env != entry->env ) {
/* this DOES happen!!! */ /* this DOES happen!!! */
XP_LOGF( "%s (ti=%p): replacing env %p with env %p for thread %x", RAW_LOG( "(ti=%p): replacing env %p with env %p for thread %x",
__func__, ti, entry->env, env, (int)self ); ti, entry->env, env, (int)self );
entry->env = env; entry->env = env;
} }
} }
@ -180,7 +180,7 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller )
ti->entries = entries; ti->entries = entries;
ti->nEntries = nEntries; ti->nEntries = nEntries;
#ifdef LOG_MAPPING #ifdef LOG_MAPPING
XP_LOGF( "%s: num env entries now %d", __func__, nEntries ); RAW_LOG( "num env entries now %d", nEntries );
#endif #endif
} }
@ -191,9 +191,9 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller )
++firstEmpty->refcount; ++firstEmpty->refcount;
#ifdef LOG_MAPPING #ifdef LOG_MAPPING
firstEmpty->ownerFunc = caller; firstEmpty->ownerFunc = caller;
XP_LOGF( "%s: entry %zu: mapped env %p to thread %x", __func__, RAW_LOG( "entry %zu: mapped env %p to thread %x",
firstEmpty - ti->entries, env, (int)self ); firstEmpty - ti->entries, env, (int)self );
XP_LOGF( "%s: num entries USED now %d", __func__, countUsed(ti) ); RAW_LOG( "num entries USED now %d", countUsed(ti) );
#endif #endif
} }
@ -221,9 +221,9 @@ map_remove_prv( EnvThreadInfo* ti, JNIEnv* env, const char* func )
if ( found ) { if ( found ) {
XP_ASSERT( pthread_self() == entry->owner ); XP_ASSERT( pthread_self() == entry->owner );
#ifdef LOG_MAPPING #ifdef LOG_MAPPING
XP_LOGF( "%s: UNMAPPED env %p to thread %x (from %s; mapped by %s)", __func__, RAW_LOG( "UNMAPPED env %p to thread %x (from %s; mapped by %s)",
entry->env, (int)entry->owner, func, entry->ownerFunc ); entry->env, (int)entry->owner, func, entry->ownerFunc );
XP_LOGF( "%s: %d entries left", __func__, countUsed( ti ) ); RAW_LOG( "%d entries left", countUsed( ti ) );
entry->ownerFunc = NULL; entry->ownerFunc = NULL;
#endif #endif
XP_ASSERT( 1 == entry->refcount ); XP_ASSERT( 1 == entry->refcount );
@ -258,6 +258,46 @@ prvEnvForMe( EnvThreadInfo* ti )
return result; return result;
} }
#ifdef DEBUG
static pthread_mutex_t g_globalStateLock = PTHREAD_MUTEX_INITIALIZER;
static JNIGlobalState* g_globalState = NULL;
void setGlobalState( JNIGlobalState* state )
{
pthread_mutex_lock( &g_globalStateLock );
g_globalState = state;
pthread_mutex_unlock( &g_globalStateLock );
}
JNIEnv*
waitEnvFromGlobals() /* hanging */
{
JNIEnv* result = NULL;
pthread_mutex_lock( &g_globalStateLock );
JNIGlobalState* state = g_globalState;
if ( !!state ) {
result = prvEnvForMe( &state->ti );
}
if ( !result ) {
pthread_mutex_unlock( &g_globalStateLock );
}
return result;
}
void
releaseEnvFromGlobals( JNIEnv* env )
{
XP_ASSERT( !!env );
JNIGlobalState* state = g_globalState;
XP_ASSERT( !!state );
XP_ASSERT( env == prvEnvForMe( &state->ti ) );
pthread_mutex_unlock( &g_globalStateLock );
}
#else
# define setGlobalState(s)
#endif
JNIEnv* JNIEnv*
envForMe( EnvThreadInfo* ti, const char* caller ) envForMe( EnvThreadInfo* ti, const char* caller )
{ {
@ -325,6 +365,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_initGlobals
globalState->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) ); globalState->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) );
globalState->smsProto = smsproto_init( MPPARM( mpool ) globalState->dutil ); globalState->smsProto = smsproto_init( MPPARM( mpool ) globalState->dutil );
MPASSIGN( globalState->mpool, mpool ); MPASSIGN( globalState->mpool, mpool );
setGlobalState( globalState );
// LOG_RETURNF( "%p", globalState ); // LOG_RETURNF( "%p", globalState );
return (jlong)globalState; return (jlong)globalState;
} }
@ -335,6 +376,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals
{ {
// LOG_FUNC(); // LOG_FUNC();
if ( 0 != jniGlobalPtr ) { if ( 0 != jniGlobalPtr ) {
setGlobalState( NULL );
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
MemPoolCtx* mpool = GETMPOOL( globalState ); MemPoolCtx* mpool = GETMPOOL( globalState );
@ -984,7 +1026,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame
} }
globals->dctx = dctx; globals->dctx = dctx;
globals->xportProcs = makeXportProcs( MPPARM(mpool) ti, j_procs ); globals->xportProcs = makeXportProcs( MPPARM(mpool) ti, j_procs );
CommonPrefs cp; CommonPrefs cp = {0};
loadCommonPrefs( env, &cp, j_cp ); loadCommonPrefs( env, &cp, j_cp );
game_makeNewGame( MPPARM(mpool) &state->game, gi, globals->util, dctx, &cp, game_makeNewGame( MPPARM(mpool) &state->game, gi, globals->util, dctx, &cp,

View file

@ -204,7 +204,7 @@ def getOrderedApks( path, appID, debug ):
files = ((os.stat(apk).st_mtime, apk) for apk in glob.glob(pattern)) files = ((os.stat(apk).st_mtime, apk) for apk in glob.glob(pattern))
for mtime, file in sorted(files, reverse=True): for mtime, file in sorted(files, reverse=True):
info = getAAPTInfo(file) info = getAAPTInfo(file)
if info['appID'] == appID: if info and 'appID' in info and info['appID'] == appID:
apkToCode[file] = info['versionCode'] apkToCode[file] = info['versionCode']
apkToMtime[file] = mtime apkToMtime[file] = mtime
result = sorted(apkToCode.keys(), reverse=True, key=lambda file: (apkToCode[file], apkToMtime[file])) result = sorted(apkToCode.keys(), reverse=True, key=lambda file: (apkToCode[file], apkToMtime[file]))
@ -326,7 +326,7 @@ def getApp( params, name = None, debug = False):
apache.log_error( "name: %s; installer: %s; gvers: %s" apache.log_error( "name: %s; installer: %s; gvers: %s"
% (name, installer, vers) ) % (name, installer, vers) )
print "name: %s; installer: %s; vers: %s" % (name, installer, vers) print "name: %s; installer: %s; vers: %s" % (name, installer, vers)
dir = k_filebase + k_apkDir + 'rel/' dir = k_filebase + k_apkDir
apk = getNextAfter( dir, name, vers, debug ) apk = getNextAfter( dir, name, vers, debug )
if apk: if apk:
apk = apk[len(k_filebase):] # strip fs path apk = apk[len(k_filebase):] # strip fs path
@ -569,7 +569,7 @@ def getUpdates( req, params ):
# result[k_XLATEINFO] = xlateResult; # result[k_XLATEINFO] = xlateResult;
result = json.dumps( result ) result = json.dumps( result )
# apache.log_error( result ) apache.log_error( 'getUpdates() => ' + result )
return result return result
def clearShelf(): def clearShelf():

View file

@ -1040,19 +1040,13 @@ typedef struct _BadWordList {
} BadWordList; } BadWordList;
static void static void
saveBadWords( const XP_UCHAR* word, XP_Bool isLegal, saveBadWords( const WNParams* wnp, void* closure )
const DictionaryCtxt* XP_UNUSED(dict),
#ifdef XWFEATURE_BOARDWORDS
const MoveInfo* XP_UNUSED(movei),
XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end),
#endif
void* closure )
{ {
if ( !isLegal ) { if ( !wnp->isLegal ) {
BadWordList* bwlp = (BadWordList*)closure; BadWordList* bwlp = (BadWordList*)closure;
bwlp->bwi.words[bwlp->bwi.nWords] = &bwlp->buf[bwlp->index]; bwlp->bwi.words[bwlp->bwi.nWords] = &bwlp->buf[bwlp->index];
XP_STRCAT( &bwlp->buf[bwlp->index], word ); XP_STRCAT( &bwlp->buf[bwlp->index], wnp->word );
bwlp->index += XP_STRLEN(word) + 1; bwlp->index += XP_STRLEN(wnp->word) + 1;
++bwlp->bwi.nWords; ++bwlp->bwi.nWords;
} }
} /* saveBadWords */ } /* saveBadWords */
@ -1149,7 +1143,7 @@ board_commitTurn( BoardCtxt* board, XP_Bool phoniesConfirmed,
bwl.bwi.dictName = bwl.bwi.dictName =
dict_getShortName( model_getPlayerDict( model, selPlayer ) ); dict_getShortName( model_getPlayerDict( model, selPlayer ) );
util_notifyIllegalWords( board->util, &bwl.bwi, selPlayer, XP_FALSE ); util_notifyIllegalWords( board->util, &bwl.bwi, selPlayer, XP_FALSE );
} else { } else if ( legal ) {
/* Hide the tray so no peeking. Leave it hidden even if user /* Hide the tray so no peeking. Leave it hidden even if user
cancels as otherwise another player could get around cancels as otherwise another player could get around
passwords and peek at tiles. */ passwords and peek at tiles. */
@ -1341,7 +1335,7 @@ timerFiredForPen( BoardCtxt* board )
if ( dragDropIsBeingDragged( board, col, row, NULL ) ) { if ( dragDropIsBeingDragged( board, col, row, NULL ) ) {
/* even if we aren't calling dragDropSetAdd we want to avoid /* even if we aren't calling dragDropSetAdd we want to avoid
putting up a sqare bonus if we're on a sqare with putting up a square bonus if we're on a square with
something that can be dragged */ something that can be dragged */
#ifdef XWFEATURE_RAISETILE #ifdef XWFEATURE_RAISETILE
draw = dragDropSetAdd( board ); draw = dragDropSetAdd( board );
@ -1351,20 +1345,24 @@ timerFiredForPen( BoardCtxt* board )
/* We calculate words even for a pending tile set, meaning /* We calculate words even for a pending tile set, meaning
dragDrop might be happening too. */ dragDrop might be happening too. */
XP_Bool listWords = XP_FALSE; XP_Bool listWords = XP_FALSE;
#ifdef XWFEATURE_BOARDWORDS /* here it is */ #ifdef XWFEATURE_BOARDWORDS
XP_U16 modelCol, modelRow; XP_U16 modelCol, modelRow;
flipIf( board, col, row, &modelCol, &modelRow ); flipIf( board, col, row, &modelCol, &modelRow );
listWords = model_getTile( board->model, modelCol, modelRow, listWords = model_getTile( board->model, modelCol, modelRow,
XP_TRUE, board->selPlayer, NULL, XP_TRUE, board->selPlayer, NULL,
NULL, NULL, NULL ); NULL, NULL, NULL );
if ( listWords ) { if ( listWords ) {
XP_LOGF( "%s(): listWords came back true", __func__ );
XWStreamCtxt* stream = XWStreamCtxt* stream =
mem_stream_make_raw( MPPARM(board->mpool) mem_stream_make_raw( MPPARM(board->mpool)
dutil_getVTManager(board->dutil) ); dutil_getVTManager(board->dutil) );
model_listWordsThrough( board->model, modelCol, modelRow, listWords = model_listWordsThrough( board->model, modelCol, modelRow,
board->selPlayer, stream ); board->selPlayer, stream );
if ( listWords ) {
util_cellSquareHeld( board->util, stream ); util_cellSquareHeld( board->util, stream );
if ( dragDropInProgress( board ) ) {
dragDropEnd( board, board->penDownX, board->penDownY, NULL );
}
}
stream_destroy( stream ); stream_destroy( stream );
} }
#endif #endif
@ -3030,7 +3028,6 @@ handlePenUpInternal( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool isPen,
XP_Bool altDown ) XP_Bool altDown )
{ {
XP_Bool draw = XP_FALSE; XP_Bool draw = XP_FALSE;
XP_Bool dragged = XP_FALSE;
BoardObjectType prevObj = board->penDownObject; BoardObjectType prevObj = board->penDownObject;
/* prevent timer from firing after pen lifted. Set now rather than later /* prevent timer from firing after pen lifted. Set now rather than later
@ -3038,6 +3035,7 @@ handlePenUpInternal( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool isPen,
exiting this function (which might give timer time to fire. */ exiting this function (which might give timer time to fire. */
board->penDownObject = OBJ_NONE; board->penDownObject = OBJ_NONE;
XP_Bool dragged = XP_FALSE;
if ( dragDropInProgress(board) ) { if ( dragDropInProgress(board) ) {
draw = dragDropEnd( board, xx, yy, &dragged ); draw = dragDropEnd( board, xx, yy, &dragged );
} }

View file

@ -140,8 +140,12 @@ typedef XP_U8 DeviceRole;
enum { enum {
PHONIES_IGNORE, PHONIES_IGNORE,
/* You can commit a phony after viewing a warning */
PHONIES_WARN, PHONIES_WARN,
PHONIES_DISALLOW /* You can commit a phony, but you'll lose your turn */
PHONIES_DISALLOW,
/* a phony is an illegal move, like tiles out-of-line */
PHONIES_BLOCK,
}; };
typedef XP_U8 XWPhoniesChoice; typedef XP_U8 XWPhoniesChoice;
@ -233,6 +237,7 @@ typedef struct CommonPrefs {
#ifdef XWFEATURE_CROSSHAIRS #ifdef XWFEATURE_CROSSHAIRS
XP_Bool hideCrosshairs; /* applies to all games */ XP_Bool hideCrosshairs; /* applies to all games */
#endif #endif
XP_U16 makePhonyPct;
} CommonPrefs; } CommonPrefs;
typedef struct _PlayerDicts { typedef struct _PlayerDicts {

View file

@ -126,5 +126,24 @@ devIDTypeToStr(DevIDType typ)
} }
#undef CASESTR #undef CASESTR
typedef void (*ProcPtr)();
void
assertTableFull( void* table, size_t sizeInBytes, const XP_UCHAR* tableName )
{
if ( 0 != sizeInBytes % sizeof(ProcPtr) ) {
XP_LOGFF( "bad call? vtable size: %zu; proc ptr size: %zu", sizeInBytes, sizeof(ProcPtr) );
XP_ASSERT( 0 );
}
#endif ProcPtr* proc = (ProcPtr*)table;
int count = sizeInBytes / sizeof(ProcPtr);
for ( int ii = 0; ii < count; ++ii ) {
if ( !*proc ) {
XP_LOGFF( "%s.vtable[%d] missing", tableName, ii );
XP_ASSERT( 0 );
}
++proc;
}
}
#endif /* DEBUG */

View file

@ -53,4 +53,10 @@ const char* devIDTypeToStr(DevIDType typ);
# define SET_DIRTY( ptr ) # define SET_DIRTY( ptr )
# endif # endif
# ifdef DEBUG
void assertTableFull( void* table, size_t sizeInBytes, const XP_UCHAR* tableName );
# else
# define assertTableFull( table, sizeInBytes, tableName )
# endif
#endif #endif

View file

@ -237,7 +237,7 @@ dragDropContinue( BoardCtxt* board, XP_U16 xx, XP_U16 yy )
} }
XP_Bool XP_Bool
dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged ) dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* draggedP )
{ {
DragState* ds = &board->dragState; DragState* ds = &board->dragState;
BoardObjectType newObj; BoardObjectType newObj;
@ -245,7 +245,9 @@ dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged )
XP_ASSERT( dragDropInProgress(board) ); XP_ASSERT( dragDropInProgress(board) );
(void)dragDropContinueImpl( board, xx, yy, &newObj ); (void)dragDropContinueImpl( board, xx, yy, &newObj );
*dragged = ds->didMove; if ( !!draggedP ) {
*draggedP = ds->didMove;
}
/* If we've dropped on something, put the tile there! Since we /* If we've dropped on something, put the tile there! Since we
don't remove it from its earlier location until it's dropped, don't remove it from its earlier location until it's dropped,

View file

@ -1104,16 +1104,10 @@ considerMove( EngineCtxt* engine, Tile* tiles, XP_S16 tileLength,
} /* considerMove */ } /* considerMove */
static void static void
countWords( const XP_UCHAR* XP_UNUSED(word), XP_Bool isLegal, countWords( const WNParams* wnp, void* closure )
const DictionaryCtxt* XP_UNUSED(dict),
#ifdef XWFEATURE_BOARDWORDS
const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start),
XP_U16 XP_UNUSED(end),
#endif
void* closure )
{ {
XP_U16* wcp = (XP_U16*)closure; XP_U16* wcp = (XP_U16*)closure;
if ( isLegal ) { if ( wnp->isLegal ) {
++*wcp; ++*wcp;
} }
} }

View file

@ -536,6 +536,7 @@ gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI )
void void
gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere ) gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere )
{ {
LOGGI( gi, "before" );
XP_ASSERT( nTotal <= MAX_NUM_PLAYERS ); XP_ASSERT( nTotal <= MAX_NUM_PLAYERS );
XP_ASSERT( nHere < nTotal ); XP_ASSERT( nHere < nTotal );
@ -549,14 +550,23 @@ gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere )
} }
if ( nHere != curLocal ) { if ( nHere != curLocal ) {
/* This will happen when a device has more than on player. Not sure I /* This will happen when a device has more than one player. Not sure I
handle that correctly, but don't assert for now. */ handle that correctly, but don't assert for now. */
XP_LOGFF( "nHere: %d; curLocal: %d; a problem?", nHere, curLocal ); XP_LOGFF( "nHere: %d; curLocal: %d; a problem?", nHere, curLocal );
/* for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { */ for ( XP_U16 ii = 0; ii < nTotal; ++ii ) {
/* gi->players[ii].isLocal = ii < nHere; */ if ( !gi->players[ii].isLocal ) {
/* } */ gi->players[ii].isLocal = XP_TRUE;
XP_LOGFF( "making player #%d local when wasn't before", ii );
++curLocal;
XP_ASSERT( curLocal <= nHere );
if ( curLocal == nHere ) {
break;
} }
} }
}
}
LOGGI( gi, "after" );
}
XP_U16 XP_U16
gi_countLocalPlayers( const CurGameInfo* gi, XP_Bool humanOnly ) gi_countLocalPlayers( const CurGameInfo* gi, XP_Bool humanOnly )
@ -762,10 +772,10 @@ player_timePenalty( CurGameInfo* gi, XP_U16 playerNum )
#ifdef DEBUG #ifdef DEBUG
void void
game_logGI( const CurGameInfo* gi, const char* msg ) game_logGI( const CurGameInfo* gi, const char* msg, const char* func, int line )
{ {
XP_LOGFF( "msg: %s", msg ); XP_LOGFF( "msg: %s from %s() line %d; addr: %p", msg, func, line, gi );
if ( !!gi ) {
XP_LOGF( " nPlayers: %d", gi->nPlayers ); XP_LOGF( " nPlayers: %d", gi->nPlayers );
for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) { for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) {
const LocalPlayer* lp = &gi->players[ii]; const LocalPlayer* lp = &gi->players[ii];
@ -777,6 +787,7 @@ game_logGI( const CurGameInfo* gi, const char* msg )
XP_LOGF( " gameID: %d", gi->gameID ); XP_LOGF( " gameID: %d", gi->gameID );
XP_LOGF( " dictName: %s", gi->dictName ); XP_LOGF( " dictName: %s", gi->dictName );
} }
}
#endif #endif
#ifdef CPLUS #ifdef CPLUS

View file

@ -64,9 +64,11 @@ typedef struct CurGameInfo {
} CurGameInfo; } CurGameInfo;
#ifdef DEBUG #ifdef DEBUG
void game_logGI( const CurGameInfo* gi, const char* msg ); # define LOGGI( gip, msg ) game_logGI( (gip), (msg), __func__, __LINE__ )
void game_logGI( const CurGameInfo* gi, const char* msg,
const char* func, int line );
#else #else
# define game_logGI(gi, msg) # define LOGGI(gi, msg)
#endif #endif
#ifdef CPLUS #ifdef CPLUS

View file

@ -77,13 +77,7 @@ static void loadPlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream,
static void writePlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, static void writePlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream,
const PlayerCtxt* pc ); const PlayerCtxt* pc );
static XP_U16 model_getRecentPassCount( ModelCtxt* model ); static XP_U16 model_getRecentPassCount( ModelCtxt* model );
static void recordWord( const XP_UCHAR* word, XP_Bool isLegal, static void recordWord( const WNParams* wnp, void *closure );
const DictionaryCtxt* dict,
#ifdef XWFEATURE_BOARDWORDS
const MoveInfo* movei, XP_U16 start, XP_U16 end,
#endif
void* clsur );
#ifdef DEBUG #ifdef DEBUG
typedef struct _DiffTurnState { typedef struct _DiffTurnState {
XP_S16 lastPlayerNum; XP_S16 lastPlayerNum;
@ -908,6 +902,8 @@ model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, XP_U16* turn )
stack_popEntry( stack, &entry ); stack_popEntry( stack, &entry );
XP_ASSERT( entry.moveType == MOVE_TYPE ); XP_ASSERT( entry.moveType == MOVE_TYPE );
model_resetCurrentTurn( model, entry.playerNum );
replaceNewTiles( model, pool, entry.playerNum, &entry.u.move.newTiles ); replaceNewTiles( model, pool, entry.playerNum, &entry.u.move.newTiles );
XP_ASSERT( !model->vol.gi->inDuplicateMode ); XP_ASSERT( !model->vol.gi->inDuplicateMode );
undoFromMove( model, entry.playerNum, blankTile, &entry.u.move ); undoFromMove( model, entry.playerNum, blankTile, &entry.u.move );
@ -1259,6 +1255,20 @@ juggleMoveIfDebug( MoveInfo* move )
} }
} }
} }
/* Reverse the *letters on* the tiles */
void
reverseTiles( MoveInfo* move )
{
MoveInfoTile* start = &move->tiles[0];
MoveInfoTile* end = start + move->nTiles - 1;
while ( start < end ) {
Tile tmp = start->tile;
start->tile = end->tile;
end->tile = tmp;
--end; ++start;
}
}
#endif #endif
void void
@ -1562,6 +1572,9 @@ model_setBlankValue( ModelCtxt* model, XP_U16 turn,
&nUsed, tfaces, tiles ); &nUsed, tfaces, tiles );
pt->tile = tiles[newIndex] | TILE_BLANK_BIT; pt->tile = tiles[newIndex] | TILE_BLANK_BIT;
/* force a recalc in case phonies==PHONIES_BLOCK */
invalidateScore( model, turn );
} }
break; break;
} }
@ -2465,17 +2478,11 @@ typedef struct _FirstWordData {
} FirstWordData; } FirstWordData;
static void static void
getFirstWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), getFirstWord( const WNParams* wnp, void* closure )
const DictionaryCtxt* XP_UNUSED(dict),
#ifdef XWFEATURE_BOARDWORDS
const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start),
XP_U16 XP_UNUSED(end),
#endif
void* closure )
{ {
FirstWordData* data = (FirstWordData*)closure; FirstWordData* data = (FirstWordData*)closure;
if ( '\0' == data->word[0] && '\0' != word[0] ) { if ( '\0' == data->word[0] && '\0' != wnp->word[0] ) {
XP_STRCAT( data->word, word ); XP_STRCAT( data->word, wnp->word );
} }
} }
@ -2560,17 +2567,10 @@ appendWithCR( XWStreamCtxt* stream, const XP_UCHAR* word, XP_U16* counter )
} }
static void static void
recordWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), recordWord( const WNParams* wnp, void* closure )
const DictionaryCtxt* XP_UNUSED(dict),
#ifdef XWFEATURE_BOARDWORDS
const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start),
XP_U16 XP_UNUSED(end),
#endif
void* closure )
{ {
RecordWordsInfo* info = (RecordWordsInfo*)closure; RecordWordsInfo* info = (RecordWordsInfo*)closure;
appendWithCR( info->stream, word, &info->nWords ); appendWithCR( info->stream, wnp->word, &info->nWords );
} }
WordNotifierInfo* WordNotifierInfo*
@ -2591,32 +2591,31 @@ typedef struct _ListWordsThroughInfo {
} ListWordsThroughInfo; } ListWordsThroughInfo;
static void static void
listWordsThrough( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), listWordsThrough( const WNParams* wnp, void* closure )
const DictionaryCtxt* XP_UNUSED(dict),
const MoveInfo* movei, XP_U16 start, XP_U16 end,
void* closure )
{ {
ListWordsThroughInfo* info = (ListWordsThroughInfo*)closure; ListWordsThroughInfo* info = (ListWordsThroughInfo*)closure;
const MoveInfo* movei = wnp->movei;
XP_Bool contained = XP_FALSE; XP_Bool contained = XP_FALSE;
if ( movei->isHorizontal && movei->commonCoord == info->row ) { if ( movei->isHorizontal && movei->commonCoord == info->row ) {
contained = start <= info->col && end >= info->col; contained = wnp->start <= info->col && wnp->end >= info->col;
} else if ( !movei->isHorizontal && movei->commonCoord == info->col ) { } else if ( !movei->isHorizontal && movei->commonCoord == info->col ) {
contained = start <= info->row && end >= info->row; contained = wnp->start <= info->row && wnp->end >= info->row;
} }
if ( contained ) { if ( contained ) {
appendWithCR( info->stream, word, &info->nWords ); appendWithCR( info->stream, wnp->word, &info->nWords );
} }
} }
/* List every word played that includes the tile on {col,row}. /* List every word played that includes the tile on {col,row}.
* *
* How? Undo backwards until we find the move that placed that tile.*/ * How? Undo backwards until we find the move that placed that tile.*/
void XP_Bool
model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row,
XP_S16 turn, XWStreamCtxt* stream ) XP_S16 turn, XWStreamCtxt* stream )
{ {
XP_Bool found = XP_FALSE;
ModelCtxt* tmpModel = makeTmpModel( model, NULL, NULL, NULL, NULL ); ModelCtxt* tmpModel = makeTmpModel( model, NULL, NULL, NULL, NULL );
copyStack( model, tmpModel->vol.stack, model->vol.stack ); copyStack( model, tmpModel->vol.stack, model->vol.stack );
@ -2667,9 +2666,12 @@ model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row,
modelAddEntry( tmpModel, nEntriesAfter++, &entry, XP_FALSE, NULL, &ni, modelAddEntry( tmpModel, nEntriesAfter++, &entry, XP_FALSE, NULL, &ni,
NULL, NULL, NULL ); NULL, NULL, NULL );
} }
XP_LOGFF( "nWords: %d", lwtInfo.nWords );
found = 0 < lwtInfo.nWords;
} }
model_destroy( tmpModel ); model_destroy( tmpModel );
return found;
} /* model_listWordsThrough */ } /* model_listWordsThrough */
#endif #endif

View file

@ -57,7 +57,7 @@ extern "C" {
#define MAX_NUM_BLANKS 4 #define MAX_NUM_BLANKS 4
/* Used by scoring code and engine as fast representation of moves. */ /* Used by scoring code and engine as fast representation of moves. */
typedef struct MoveInfoTile { typedef struct _MoveInfoTile {
XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */ XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */
Tile tile; /* 6 bits will do */ Tile tile; /* 6 bits will do */
} MoveInfoTile; } MoveInfoTile;
@ -237,6 +237,7 @@ void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum,
#ifdef DEBUG #ifdef DEBUG
void juggleMoveIfDebug( MoveInfo* move ); void juggleMoveIfDebug( MoveInfo* move );
void reverseTiles( MoveInfo* move );
void model_dumpSelf( const ModelCtxt* model, const XP_UCHAR* msg ); void model_dumpSelf( const ModelCtxt* model, const XP_UCHAR* msg );
#else #else
# define juggleMoveIfDebug(newMove) # define juggleMoveIfDebug(newMove)
@ -282,13 +283,18 @@ void model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts,
/********************* scoring ********************/ /********************* scoring ********************/
typedef void (*WordNotifierProc)( const XP_UCHAR* word, XP_Bool isLegal, typedef struct _WNParams {
const DictionaryCtxt* dict, const XP_UCHAR* word;
XP_Bool isLegal;
const DictionaryCtxt* dict;
#ifdef XWFEATURE_BOARDWORDS #ifdef XWFEATURE_BOARDWORDS
const MoveInfo* movei, XP_U16 start, const MoveInfo* movei;
XP_U16 end, XP_U16 start;
XP_U16 end;
#endif #endif
void* closure ); } WNParams;
typedef void (*WordNotifierProc)( const WNParams* wnp, void* closure );
typedef struct WordNotifierInfo { typedef struct WordNotifierInfo {
WordNotifierProc proc; WordNotifierProc proc;
void* closure; void* closure;
@ -302,7 +308,7 @@ XP_S16 model_getPlayerScore( ModelCtxt* model, XP_S16 player );
XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player,
LastMoveInfo* info ); LastMoveInfo* info );
#ifdef XWFEATURE_BOARDWORDS #ifdef XWFEATURE_BOARDWORDS
void model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, XP_Bool model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row,
XP_S16 turn, XWStreamCtxt* stream ); XP_S16 turn, XWStreamCtxt* stream );
#endif #endif

View file

@ -217,6 +217,24 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
} }
} /* model_figureFinalScores */ } /* model_figureFinalScores */
typedef struct _BlockCheckState {
WordNotifierInfo* chainNI;
XP_UCHAR word[32];
} BlockCheckState;
static void
blockCheck( const WNParams* wnp, void* closure )
{
BlockCheckState* bcs = (BlockCheckState*)closure;
if ( !!bcs->chainNI ) {
(bcs->chainNI->proc)( wnp, bcs->chainNI->closure );
}
if ( !wnp->isLegal && '\0' == bcs->word[0] ) {
XP_STRCAT( bcs->word, wnp->word );
}
}
/* checkScoreMove. /* checkScoreMove.
* Negative score means illegal. * Negative score means illegal.
*/ */
@ -238,17 +256,39 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine,
formatSummary( stream, model, 0 ); formatSummary( stream, model, 0 );
} }
} else if ( tilesInLine( model, turn, &isHorizontal ) ) { } else if ( !tilesInLine( model, turn, &isHorizontal ) ) {
if ( !silent ) { /* tiles out of line */
util_userError( model->vol.util, ERR_TILES_NOT_IN_LINE );
}
} else {
MoveInfo moveInfo; MoveInfo moveInfo;
normalizeMoves( model, turn, isHorizontal, &moveInfo ); normalizeMoves( model, turn, isHorizontal, &moveInfo );
if ( isLegalMove( model, &moveInfo, silent ) ) { if ( isLegalMove( model, &moveInfo, silent ) ) {
score = figureMoveScore( model, turn, &moveInfo, engine, stream, /* If I'm testing for blocking, I need to chain my test onto any
notifyInfo ); existing WordNotifierInfo. blockCheck() does that. */
XP_Bool checkDict = PHONIES_BLOCK == model->vol.gi->phoniesAction;
WordNotifierInfo blockWNI;
BlockCheckState bcs;
if ( checkDict ) {
XP_MEMSET( &bcs, 0, sizeof(bcs) );
bcs.chainNI = notifyInfo;
blockWNI.proc = blockCheck;
blockWNI.closure = &bcs;
notifyInfo = &blockWNI;
}
XP_S16 tmpScore = figureMoveScore( model, turn, &moveInfo,
engine, stream, notifyInfo );
if ( checkDict && '\0' != bcs.word[0] ) {
if ( !silent ) {
DictionaryCtxt* dict = model_getPlayerDict( model, turn );
util_informWordBlocked( model->vol.util, bcs.word, dict_getName( dict ) );
}
} else {
score = tmpScore;
}
} }
} else if ( !silent ) { /* tiles out of line */
util_userError( model->vol.util, ERR_TILES_NOT_IN_LINE );
} }
return score; return score;
} /* checkScoreMove */ } /* checkScoreMove */
@ -695,11 +735,13 @@ scoreWord( const ModelCtxt* model, XP_U16 turn,
XP_UCHAR buf[(MAX_ROWS*2)+1]; XP_UCHAR buf[(MAX_ROWS*2)+1];
dict_tilesToString( dict, checkWordBuf, len, buf, dict_tilesToString( dict, checkWordBuf, len, buf,
sizeof(buf) ); sizeof(buf) );
(void)(*notifyInfo->proc)( buf, legal, dict,
WNParams wnp = { .word = buf, .isLegal = legal, .dict = dict,
#ifdef XWFEATURE_BOARDWORDS #ifdef XWFEATURE_BOARDWORDS
movei, start, end, .movei = movei, .start = start, .end = end,
#endif #endif
notifyInfo->closure ); };
(void)(*notifyInfo->proc)( &wnp, notifyInfo->closure );
} }
if ( !!stream ) { if ( !!stream ) {

View file

@ -33,14 +33,14 @@
void void
nli_init( NetLaunchInfo* nli, const CurGameInfo* gi, const CommsAddrRec* addr, nli_init( NetLaunchInfo* nli, const CurGameInfo* gi, const CommsAddrRec* addr,
XP_U16 nPlayers, XP_U16 forceChannel ) XP_U16 nPlayersH, XP_U16 forceChannel )
{ {
XP_MEMSET( nli, 0, sizeof(*nli) ); XP_MEMSET( nli, 0, sizeof(*nli) );
nli->gameID = gi->gameID; nli->gameID = gi->gameID;
XP_STRCAT( nli->dict, gi->dictName ); XP_STRCAT( nli->dict, gi->dictName );
nli->lang = gi->dictLang; nli->lang = gi->dictLang;
nli->nPlayersT = gi->nPlayers; nli->nPlayersT = gi->nPlayers;
nli->nPlayersH = nPlayers; nli->nPlayersH = nPlayersH;
nli->forceChannel = forceChannel; nli->forceChannel = forceChannel;
nli->inDuplicateMode = gi->inDuplicateMode; nli->inDuplicateMode = gi->inDuplicateMode;
@ -89,6 +89,7 @@ nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID )
void void
nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream ) nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream )
{ {
LOGNLI( nli );
stream_putU8( stream, NLI_VERSION ); stream_putU8( stream, NLI_VERSION );
stream_putU16( stream, nli->_conTypes ); stream_putU16( stream, nli->_conTypes );
@ -172,6 +173,7 @@ nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream )
XP_ASSERT( 0 == stream_getSize( stream ) ); XP_ASSERT( 0 == stream_getSize( stream ) );
LOG_RETURNF( "%s", boolToStr(success) ); LOG_RETURNF( "%s", boolToStr(success) );
LOGNLI( nli );
return success; return success;
} }
@ -202,4 +204,18 @@ nli_makeAddrRec( const NetLaunchInfo* nli, CommsAddrRec* addr )
} }
} }
} }
# ifdef DEBUG
void
logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerLine )
{
XP_LOGFF( "called by %s(), line %d", callerFunc, callerLine );
XP_UCHAR buf[256];
XP_SNPRINTF( buf, VSIZE(buf), "{nPlayersT: %d; nPlayersH: %d; "
"gameID: %d; inviteID: %s}",
nli->nPlayersT, nli->nPlayersH, nli->gameID, nli->inviteID );
XP_LOGF( "%s", buf );
}
# endif
#endif #endif

View file

@ -80,5 +80,13 @@ void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID );
void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID ); void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID );
void nli_setGameName( NetLaunchInfo* invit, const XP_UCHAR* gameName ); void nli_setGameName( NetLaunchInfo* invit, const XP_UCHAR* gameName );
# ifdef DEBUG
void logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerLine );
# define LOGNLI(nli) \
logNLI( (nli), __func__, __LINE__ )
# else
# define LOGNLI(nli)
# endif
#endif #endif

View file

@ -231,7 +231,8 @@ pool_containsTiles( const PoolContext* pool, const TrayTileSet* tiles )
XP_U16 XP_U16
pool_getNTilesLeft( const PoolContext* pool ) pool_getNTilesLeft( const PoolContext* pool )
{ {
return pool->numTilesLeft; XP_ASSERT( !!pool );
return NULL == pool ? 0 : pool->numTilesLeft;
} /* pool_remainingTileCount */ } /* pool_remainingTileCount */
XP_U16 XP_U16

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */ /* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */
/* /*
* Copyright 1997 - 2019 by Eric House (xwords@eehouse.org). All rights * Copyright 1997 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -106,6 +106,7 @@ typedef struct ServerNonvolatiles {
XP_U16 robotThinkMin, robotThinkMax; /* not saved (yet) */ XP_U16 robotThinkMin, robotThinkMax; /* not saved (yet) */
XP_U16 robotTradePct; XP_U16 robotTradePct;
#endif #endif
XP_U16 makePhonyPct;
RemoteAddress addresses[MAX_NUM_PLAYERS]; RemoteAddress addresses[MAX_NUM_PLAYERS];
XWStreamCtxt* prevMoveStream; /* save it to print later */ XWStreamCtxt* prevMoveStream; /* save it to print later */
@ -122,9 +123,7 @@ struct ServerCtxt {
PoolContext* pool; PoolContext* pool;
BadWordInfo illegalWordInfo; BadWordInfo illegalWordInfo;
#ifndef XWFEATURE_STANDALONE_ONLY
XP_U16 lastMoveSource; XP_U16 lastMoveSource;
#endif
ServerPlayer players[MAX_NUM_PLAYERS]; ServerPlayer players[MAX_NUM_PLAYERS];
XP_Bool serverDoing; XP_Bool serverDoing;
@ -158,8 +157,7 @@ static void nextTurn( ServerCtxt* server, XP_S16 nxtTurn );
static void doEndGame( ServerCtxt* server, XP_S16 quitter ); static void doEndGame( ServerCtxt* server, XP_S16 quitter );
static void endGameInternal( ServerCtxt* server, GameEndReason why, XP_S16 quitter ); static void endGameInternal( ServerCtxt* server, GameEndReason why, XP_S16 quitter );
static void badWordMoveUndoAndTellUser( ServerCtxt* server, static void badWordMoveUndoAndTellUser( ServerCtxt* server, BadWordInfo* bwi );
BadWordInfo* bwi );
static XP_Bool tileCountsOk( const ServerCtxt* server ); static XP_Bool tileCountsOk( const ServerCtxt* server );
static void setTurn( ServerCtxt* server, XP_S16 turn ); static void setTurn( ServerCtxt* server, XP_S16 turn );
static XWStreamCtxt* mkServerStream( ServerCtxt* server ); static XWStreamCtxt* mkServerStream( ServerCtxt* server );
@ -202,6 +200,7 @@ static void writeProto( const ServerCtxt* server, XWStreamCtxt* stream,
#endif #endif
#define PICK_NEXT -1 #define PICK_NEXT -1
#define PICK_CUR -2
#if defined DEBUG && ! defined XWFEATURE_STANDALONE_ONLY #if defined DEBUG && ! defined XWFEATURE_STANDALONE_ONLY
static char* static char*
@ -226,8 +225,7 @@ getStateStr( XW_State st )
} }
#endif #endif
#if 0 #ifdef DEBUG
//def DEBUG
static void static void
logNewState( XW_State old, XW_State newst, const char* caller ) logNewState( XW_State old, XW_State newst, const char* caller )
{ {
@ -237,9 +235,9 @@ logNewState( XW_State old, XW_State newst, const char* caller )
XP_LOGFF( "state transition %s => %s (from %s())", oldStr, newStr, caller ); XP_LOGFF( "state transition %s => %s (from %s())", oldStr, newStr, caller );
} }
} }
# define SETSTATE( s, st ) { \ # define SETSTATE( server, st ) { \
XW_State old = (s)->nv.gameState; \ XW_State old = (server)->nv.gameState; \
(s)->nv.gameState = (st); \ (server)->nv.gameState = (st); \
logNewState( old, st, __func__); \ logNewState( old, st, __func__); \
} }
#else #else
@ -404,6 +402,7 @@ putNV( XWStreamCtxt* stream, const ServerNonvolatiles* nv, XP_U16 nPlayers )
stream_putBits( stream, XWSTATE_NBITS, nv->stateAfterShow ); stream_putBits( stream, XWSTATE_NBITS, nv->stateAfterShow );
/* +1: make -1 (NOTURN) into a positive number */ /* +1: make -1 (NOTURN) into a positive number */
XP_ASSERT( -1 <= nv->currentTurn && nv->currentTurn < MAX_NUM_PLAYERS );
stream_putBits( stream, NPLAYERS_NBITS, nv->currentTurn+1 ); stream_putBits( stream, NPLAYERS_NBITS, nv->currentTurn+1 );
stream_putBits( stream, NPLAYERS_NBITS, nv->quitter+1 ); stream_putBits( stream, NPLAYERS_NBITS, nv->quitter+1 );
stream_putBits( stream, NPLAYERS_NBITS, nv->pendingRegistrations ); stream_putBits( stream, NPLAYERS_NBITS, nv->pendingRegistrations );
@ -545,11 +544,7 @@ server_writeToStream( const ServerCtxt* server, XWStreamCtxt* stream )
} }
} }
#ifndef XWFEATURE_STANDALONE_ONLY
stream_putBits( stream, 2, server->lastMoveSource ); stream_putBits( stream, 2, server->lastMoveSource );
#else
stream_putBits( stream, 2, 0 );
#endif
writeStreamIf( stream, server->nv.prevMoveStream ); writeStreamIf( stream, server->nv.prevMoveStream );
writeStreamIf( stream, server->nv.prevWordsStream ); writeStreamIf( stream, server->nv.prevWordsStream );
@ -630,6 +625,7 @@ server_prefsChanged( ServerCtxt* server, const CommonPrefs* cp )
server->nv.robotThinkMax = cp->robotThinkMax; server->nv.robotThinkMax = cp->robotThinkMax;
server->nv.robotTradePct = cp->robotTradePct; server->nv.robotTradePct = cp->robotTradePct;
#endif #endif
server->nv.makePhonyPct = cp->makePhonyPct;
} /* server_prefsChanged */ } /* server_prefsChanged */
XP_S16 XP_S16
@ -672,8 +668,9 @@ server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream )
nPlayers = gi->nPlayers; nPlayers = gi->nPlayers;
XP_ASSERT( nPlayers > 0 ); XP_ASSERT( nPlayers > 0 );
stream_putBits( stream, NPLAYERS_NBITS, XP_U16 localPlayers = gi_countLocalPlayers( gi, XP_FALSE);
gi_countLocalPlayers( gi, XP_FALSE) ); XP_ASSERT( 0 < localPlayers );
stream_putBits( stream, NPLAYERS_NBITS, localPlayers );
for ( lp = gi->players; nPlayers-- > 0; ++lp ) { for ( lp = gi->players; nPlayers-- > 0; ++lp ) {
XP_UCHAR* name; XP_UCHAR* name;
@ -1373,6 +1370,11 @@ makeRobotMove( ServerCtxt* server )
/* if canMove is false, this is a fake move, a pass */ /* if canMove is false, this is a fake move, a pass */
if ( canMove || NPASSES_OK(server) ) { if ( canMove || NPASSES_OK(server) ) {
#ifdef DEBUG
if ( server->nv.makePhonyPct > XP_RANDOM() % 100 ) {
reverseTiles( &newMove );
}
#endif
juggleMoveIfDebug( &newMove ); juggleMoveIfDebug( &newMove );
model_makeTurnFromMoveInfo( model, turn, &newMove ); model_makeTurnFromMoveInfo( model, turn, &newMove );
XP_LOGFF( "robot making %d tile move for player %d", newMove.nTiles, turn ); XP_LOGFF( "robot making %d tile move for player %d", newMove.nTiles, turn );
@ -1589,7 +1591,7 @@ server_do( ServerCtxt* server )
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
sendBadWordMsgs( server ); sendBadWordMsgs( server );
#endif #endif
nextTurn( server, PICK_NEXT ); /* sets server->nv.gameState */ nextTurn( server, PICK_NEXT );
//moreToDo = XP_TRUE; /* why? */ //moreToDo = XP_TRUE; /* why? */
break; break;
@ -1605,7 +1607,7 @@ server_do( ServerCtxt* server )
case XWSTATE_MOVE_CONFIRM_MUSTSEND: case XWSTATE_MOVE_CONFIRM_MUSTSEND:
XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER ); XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER );
tellMoveWasLegal( server ); tellMoveWasLegal( server ); /* sets state */
nextTurn( server, PICK_NEXT ); nextTurn( server, PICK_NEXT );
break; break;
@ -2049,14 +2051,14 @@ static void
bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi ) bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi )
{ {
XP_U16 nWords = stream_getBits( stream, 4 ); XP_U16 nWords = stream_getBits( stream, 4 );
const XP_UCHAR** sp = bwi->words;
bwi->nWords = nWords; bwi->nWords = nWords;
bwi->dictName = ( STREAM_VERS_DICTNAME <= stream_getVersion( stream ) ) bwi->dictName = ( STREAM_VERS_DICTNAME <= stream_getVersion( stream ) )
? stringFromStream( mpool, stream ) : NULL; ? stringFromStream( mpool, stream ) : NULL;
for ( sp = bwi->words; nWords; ++sp, --nWords ) { for ( int ii = 0; ii < nWords; ++ii ) {
*sp = (const XP_UCHAR*)stringFromStream( mpool, stream ); bwi->words[ii] = (const XP_UCHAR*)stringFromStream( mpool, stream );
} }
bwi->words[nWords] = NULL;
} /* bwiFromStream */ } /* bwiFromStream */
#ifdef DEBUG #ifdef DEBUG
@ -2121,10 +2123,15 @@ sendBadWordMsgs( ServerCtxt* server )
bwiToStream( stream, &server->illegalWordInfo ); bwiToStream( stream, &server->illegalWordInfo );
/* XP_U32 hash = model_getHash( server->vol.model ); */
/* stream_putU32( stream, hash ); */
/* XP_LOGFF( "wrote hash: %X", hash ); */
stream_destroy( stream ); stream_destroy( stream );
freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); freeBWI( MPPARM(server->mpool) &server->illegalWordInfo );
} }
SETSTATE( server, XWSTATE_INTURN );
} /* sendBadWordMsgs */ } /* sendBadWordMsgs */
#endif #endif
@ -2512,18 +2519,19 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn )
{ {
XP_LOGFF( "(nxtTurn=%d)", nxtTurn ); XP_LOGFF( "(nxtTurn=%d)", nxtTurn );
CurGameInfo* gi = server->vol.gi; CurGameInfo* gi = server->vol.gi;
XP_Bool playerTilesLeft = XP_FALSE;
XP_S16 currentTurn = server->nv.currentTurn; XP_S16 currentTurn = server->nv.currentTurn;
XP_Bool moreToDo = XP_FALSE; XP_Bool moreToDo = XP_FALSE;
if ( nxtTurn == PICK_NEXT ) { if ( nxtTurn == PICK_CUR ) {
nxtTurn = model_getNextTurn( server->vol.model );
} else if ( nxtTurn == PICK_NEXT ) {
XP_ASSERT( server->nv.gameState == XWSTATE_INTURN );
if ( server->nv.gameState != XWSTATE_INTURN ) { if ( server->nv.gameState != XWSTATE_INTURN ) {
XP_LOGFF( "doing nothing; state %s != XWSTATE_INTURN", XP_LOGFF( "doing nothing; state %s != XWSTATE_INTURN",
getStateStr(server->nv.gameState) ); getStateStr(server->nv.gameState) );
XP_ASSERT( !moreToDo ); XP_ASSERT( !moreToDo );
goto exit; goto exit;
} else if ( currentTurn >= 0 ) { } else if ( currentTurn >= 0 ) {
playerTilesLeft = tileCountsOk( server );
if ( inDuplicateMode(server) ) { if ( inDuplicateMode(server) ) {
nxtTurn = dupe_nextTurn( server ); nxtTurn = dupe_nextTurn( server );
} else { } else {
@ -2536,9 +2544,9 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn )
/* We're doing an undo, and so won't bother figuring out who the /* We're doing an undo, and so won't bother figuring out who the
previous turn was or how many tiles he had: it's a sure thing he previous turn was or how many tiles he had: it's a sure thing he
"has" enough to be allowed to take the turn just undone. */ "has" enough to be allowed to take the turn just undone. */
playerTilesLeft = XP_TRUE;
XP_ASSERT( nxtTurn == model_getNextTurn( server->vol.model ) ); XP_ASSERT( nxtTurn == model_getNextTurn( server->vol.model ) );
} }
XP_Bool playerTilesLeft = tileCountsOk( server );
SETSTATE( server, XWSTATE_INTURN ); /* even if game over, if undoing */ SETSTATE( server, XWSTATE_INTURN ); /* even if game over, if undoing */
if ( playerTilesLeft && NPASSES_OK(server) ){ if ( playerTilesLeft && NPASSES_OK(server) ){
@ -2611,24 +2619,18 @@ server_setGameOverListener( ServerCtxt* server, GameOverListener gol,
} /* server_setGameOverListener */ } /* server_setGameOverListener */
static void static void
storeBadWords( const XP_UCHAR* word, XP_Bool isLegal, storeBadWords( const WNParams* wnp, void* closure )
const DictionaryCtxt* dict,
#ifdef XWFEATURE_BOARDWORDS
const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start),
XP_U16 XP_UNUSED(end),
#endif
void* closure )
{ {
if ( !isLegal ) { if ( !wnp->isLegal ) {
ServerCtxt* server = (ServerCtxt*)closure; ServerCtxt* server = (ServerCtxt*)closure;
const XP_UCHAR* name = dict_getShortName( dict ); const XP_UCHAR* name = dict_getShortName( wnp->dict );
XP_LOGF( "storeBadWords called with \"%s\" (name=%s)", word, name ); XP_LOGF( "storeBadWords called with \"%s\" (name=%s)", wnp->word, name );
if ( NULL == server->illegalWordInfo.dictName ) { if ( NULL == server->illegalWordInfo.dictName ) {
server->illegalWordInfo.dictName = copyString( server->mpool, name ); server->illegalWordInfo.dictName = copyString( server->mpool, name );
} }
server->illegalWordInfo.words[server->illegalWordInfo.nWords++] server->illegalWordInfo.words[server->illegalWordInfo.nWords++]
= copyString( server->mpool, word ); = copyString( server->mpool, wnp->word );
} }
} /* storeBadWords */ } /* storeBadWords */
@ -3596,6 +3598,7 @@ finishMove( ServerCtxt* server, TrayTileSet* newTiles, XP_U16 turn )
} else if (isClient && (gi->phoniesAction == PHONIES_DISALLOW) } else if (isClient && (gi->phoniesAction == PHONIES_DISALLOW)
&& nTilesMoved > 0 ) { && nTilesMoved > 0 ) {
SETSTATE( server, XWSTATE_MOVE_CONFIRM_WAIT ); SETSTATE( server, XWSTATE_MOVE_CONFIRM_WAIT );
setTurn( server, -1 );
#endif #endif
} else { } else {
nextTurn( server, PICK_NEXT ); nextTurn( server, PICK_NEXT );
@ -3830,6 +3833,9 @@ setTurn( ServerCtxt* server, XP_S16 turn )
if ( inDupMode || server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) { if ( inDupMode || server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) {
if ( DUP_PLAYER == turn && inDupMode ) { if ( DUP_PLAYER == turn && inDupMode ) {
turn = dupe_nextTurn( server ); turn = dupe_nextTurn( server );
} else if ( PICK_CUR == turn ) {
XP_ASSERT( !inDupMode );
turn = model_getNextTurn( server->vol.model );
} else if ( 0 <= turn && !inDupMode ) { } else if ( 0 <= turn && !inDupMode ) {
XP_ASSERT( turn == model_getNextTurn( server->vol.model ) ); XP_ASSERT( turn == model_getNextTurn( server->vol.model ) );
} }
@ -3843,11 +3849,13 @@ setTurn( ServerCtxt* server, XP_S16 turn )
static void static void
tellMoveWasLegal( ServerCtxt* server ) tellMoveWasLegal( ServerCtxt* server )
{ {
XWStreamCtxt* stream; XWStreamCtxt* stream =
messageStreamWithHeader( server, server->lastMoveSource,
stream = messageStreamWithHeader( server, server->lastMoveSource,
XWPROTO_MOVE_CONFIRM ); XWPROTO_MOVE_CONFIRM );
stream_destroy( stream ); stream_destroy( stream );
SETSTATE( server, XWSTATE_INTURN );
} /* tellMoveWasLegal */ } /* tellMoveWasLegal */
static XP_Bool static XP_Bool
@ -3862,6 +3870,7 @@ handleIllegalWord( ServerCtxt* server, XWStreamCtxt* incoming )
freeBWI( MPPARM(server->mpool) &bwi ); freeBWI( MPPARM(server->mpool) &bwi );
SETSTATE( server, XWSTATE_INTURN );
return XP_TRUE; return XP_TRUE;
} /* handleIllegalWord */ } /* handleIllegalWord */
@ -3872,7 +3881,8 @@ handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) )
XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT );
XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT );
nextTurn( server, PICK_NEXT ); SETSTATE( server, XWSTATE_INTURN );
nextTurn( server, PICK_CUR );
return accepted; return accepted;
} /* handleMoveOk */ } /* handleMoveOk */
@ -4120,7 +4130,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming )
case XWPROTO_BADWORD_INFO: case XWPROTO_BADWORD_INFO:
accepted = handleIllegalWord( server, incoming ); accepted = handleIllegalWord( server, incoming );
if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) { if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) {
nextTurn( server, PICK_NEXT ); nextTurn( server, PICK_CUR );
} }
break; break;

View file

@ -108,6 +108,11 @@ static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex,
static void freeForPhone( SMSProto* state, const XP_UCHAR* phone ); static void freeForPhone( SMSProto* state, const XP_UCHAR* phone );
static void freeMsg( SMSProto* state, MsgRec** msg ); static void freeMsg( SMSProto* state, MsgRec** msg );
static void freeRec( SMSProto* state, ToPhoneRec* rec ); static void freeRec( SMSProto* state, ToPhoneRec* rec );
#ifdef DEBUG
static void logResult( const SMSProto* state, const SMSMsgArray* result, const char* caller );
#else
# define logResult( state, result, caller )
#endif
SMSProto* SMSProto*
smsproto_init( MPFORMAL XW_DUtilCtxt* dutil ) smsproto_init( MPFORMAL XW_DUtilCtxt* dutil )
@ -216,7 +221,7 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID,
#ifdef DEBUG #ifdef DEBUG
XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen ); XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen );
XP_LOGF( "%s(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", __func__, cmd, XP_LOGFF( "(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", cmd,
gameID, buflen, checksum, toPhone ); gameID, buflen, checksum, toPhone );
XP_FREEP( state->mpool, &checksum ); XP_FREEP( state->mpool, &checksum );
#endif #endif
@ -225,7 +230,7 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID,
/* First, add the new message (if present) to the array */ /* First, add the new message (if present) to the array */
XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil ); XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil );
if ( cmd != NONE ) { if ( cmd != NONE && 0 < buflen ) {
addToOutRec( state, rec, cmd, toPort, gameID, buf, buflen, nowSeconds ); addToOutRec( state, rec, cmd, toPort, gameID, buf, buflen, nowSeconds );
} }
@ -249,12 +254,13 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID,
} }
*waitSecsP = waitSecs; *waitSecsP = waitSecs;
XP_LOGF( "%s() => %p (len=%d, *waitSecs=%d)", __func__, result, XP_LOGF( "%s() => %p (count=%d, *waitSecs=%d)", __func__, result,
!!result ? result->nMsgs : 0, *waitSecsP ); !!result ? result->nMsgs : 0, *waitSecsP );
pthread_mutex_unlock( &state->mutex ); pthread_mutex_unlock( &state->mutex );
logResult( state, result, __func__ );
return result; return result;
} } /* smsproto_prepOutbound */
static SMSMsgArray* static SMSMsgArray*
appendLocMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgLoc* msg ) appendLocMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgLoc* msg )
@ -292,7 +298,14 @@ SMSMsgArray*
smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
XP_U16 wantPort, const XP_U8* data, XP_U16 len ) XP_U16 wantPort, const XP_U8* data, XP_U16 len )
{ {
XP_LOGF( "%s(): len=%d, fromPhone=%s", __func__, len, fromPhone ); XP_LOGFF( "len=%d, fromPhone=%s", len, fromPhone );
#ifdef DEBUG
XP_UCHAR* checksum = dutil_md5sum( state->dutil, data, len );
XP_LOGFF( "(fromPhone=%s, len=%d); sum=%s", fromPhone, len, checksum );
XP_FREEP( state->mpool, &checksum );
#endif
SMSMsgArray* result = NULL; SMSMsgArray* result = NULL;
pthread_mutex_lock( &state->mutex ); pthread_mutex_lock( &state->mutex );
@ -361,7 +374,8 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
stream_destroy( stream ); stream_destroy( stream );
XP_LOGF( "%s() => %p (len=%d)", __func__, result, (!!result) ? result->nMsgs : 0 ); XP_LOGFF( "=> %p (len=%d)", result, (!!result) ? result->nMsgs : 0 );
logResult( state, result, __func__ );
pthread_mutex_unlock( &state->mutex ); pthread_mutex_unlock( &state->mutex );
return result; return result;
@ -395,6 +409,39 @@ smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr )
pthread_mutex_unlock( &state->mutex ); pthread_mutex_unlock( &state->mutex );
} }
#ifdef DEBUG
static void
logResult( const SMSProto* state, const SMSMsgArray* result, const char* caller )
{
if ( !!result ) {
for ( int ii = 0; ii < result->nMsgs; ++ii ) {
XP_U8* data;
XP_U16 len = 0;
switch ( result->format ) {
case FORMAT_LOC: {
SMSMsgLoc* msgsLoc = &result->u.msgsLoc[ii];
data = msgsLoc->data;
len = msgsLoc->len;
}
break;
case FORMAT_NET: {
SMSMsgNet* msgsNet = &result->u.msgsNet[ii];
data = msgsNet->data;
len = msgsNet->len;
}
break;
default:
XP_ASSERT(0);
}
XP_ASSERT( 0 < len );
XP_UCHAR* checksum = dutil_md5sum( state->dutil, data, len );
XP_LOGFF( "%s() => datum[%d] sum: %s, len: %d", caller, ii, checksum, len );
XP_FREEP( state->mpool, &checksum );
}
}
}
#endif
static void static void
freeMsg( SMSProto* XP_UNUSED_DBG(state), MsgRec** msgp ) freeMsg( SMSProto* XP_UNUSED_DBG(state), MsgRec** msgp )
{ {

View file

@ -71,10 +71,11 @@ typedef struct PickInfo {
XP_U16 thisPick; /* <= nTotal */ XP_U16 thisPick; /* <= nTotal */
} PickInfo; } PickInfo;
typedef struct BadWordInfo { typedef struct _BadWordInfo {
XP_U16 nWords; XP_U16 nWords;
const XP_UCHAR* dictName; const XP_UCHAR* dictName;
const XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */ /* Null-terminated array of ptrs */
const XP_UCHAR* words[MAX_TRAY_TILES+2]; /* can form in both directions */
} BadWordInfo; } BadWordInfo;
/* XWTimerProc returns true if redraw was necessitated by what the proc did */ /* XWTimerProc returns true if redraw was necessitated by what the proc did */
@ -173,6 +174,9 @@ typedef struct UtilVtable {
void (*m_util_setIsServer)(XW_UtilCtxt* uc, XP_Bool isServer ); void (*m_util_setIsServer)(XW_UtilCtxt* uc, XP_Bool isServer );
#endif #endif
void (*m_util_informWordBlocked)( XW_UtilCtxt* uc, const XP_UCHAR* word,
const XP_UCHAR* dictName );
#ifdef XWFEATURE_SEARCHLIMIT #ifdef XWFEATURE_SEARCHLIMIT
XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc, XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc,
XP_U16* min, XP_U16* max ); XP_U16* min, XP_U16* max );
@ -308,6 +312,9 @@ struct XW_UtilCtxt {
# define util_addrChange( uc, addro, addrn ) # define util_addrChange( uc, addro, addrn )
#endif #endif
#define util_informWordBlocked(uc, w, d) \
(uc)->vtable->m_util_informWordBlocked( (uc), (w), (d) )
#ifdef XWFEATURE_SEARCHLIMIT #ifdef XWFEATURE_SEARCHLIMIT
#define util_getTraySearchLimits(uc,min,max) \ #define util_getTraySearchLimits(uc,min,max) \
(uc)->vtable->m_util_getTraySearchLimits((uc), (min), (max)) (uc)->vtable->m_util_getTraySearchLimits((uc), (min), (max))

View file

@ -0,0 +1,45 @@
# -*-mode: Makefile; -*-
# Copyright 2002-2020 by Eric House (xwords@eehouse.org). All rights
# reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
XWLANG=Hungarian
LANGCODE=hu_HU
ENC = UTF-8
TARGET_TYPE ?= WINCE
DICTNOTE = "This wordlist contains the tile information for Hungarian but no words."
LANG_SPECIAL_INFO = \
"CS Cs cS cs" /dev/null /dev/null \
"GY Gy gY gy" /dev/null /dev/null \
"LY Ly lY ly" /dev/null /dev/null \
"NY Ny nY ny" /dev/null /dev/null \
"SZ Sz sZ sz" /dev/null /dev/null \
"TY Ty tY ty" /dev/null /dev/null \
"ZS Zs zS zs" /dev/null /dev/null \
include ../Makefile.langcommon
# Empty dict
$(XWLANG)Main.dict.gz:
echo -n "" | gzip -c > $@
# Everything but creating of the Main.dict file is inherited from the
# "parent" Makefile.langcommon in the parent directory.
clean: clean_common
rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb

View file

@ -0,0 +1,50 @@
# -*- mode: conf; coding: utf-8; -*-
LANGCODE:hu_HU
CHARSET: utf-8
# High bit means "official". Next 7 bits are an enum where
# Hungarian==0x14. Low byte is padding
XLOC_HEADER:0x9400
<BEGIN_TILES>
2 0 {"_"}
6 1 'A|a'
4 1 'Á|á'
3 2 'B|b'
1 5 'C|c'
1 7 {"CS|cs"}
3 2 'D|d'
6 1 'E|e'
3 3 'É|é'
2 4 'F|f'
3 2 'G|g'
2 4 {"GY|gy"}
2 3 'H|h'
3 1 'I|i'
1 5 'Í|í'
2 4 'J|j'
6 1 'K|k'
4 1 'L|l'
1 8 {"LY|ly"}
3 1 'M|m'
4 1 'N|n'
1 5 {"NY|ny"}
3 1 'O|o'
3 2 'Ó|ó'
2 4 'Ö|ö'
1 7 'Ő|ö'
2 4 'P|p'
4 1 'R|r'
3 1 'S|s'
2 3 {"SZ|sz"}
5 1 'T|t'
1 10 {"TY|ty"}
2 4 'U|u'
1 7 'Ú|ú'
2 4 'Ü|ü'
1 7 'Ű|ű'
2 3 'V|v'
2 4 'Z|z'
1 8 {"ZS|zs"}
<END_TILES>

View file

@ -220,6 +220,7 @@ def process(args):
nodes = loadNodes( dawg, nodeSize ) nodes = loadNodes( dawg, nodeSize )
words = [] words = []
if nodes:
expandDAWG( nodes, nodeSize, offset, data, words ) expandDAWG( nodes, nodeSize, offset, data, words )
assert len(words) == nWords assert len(words) == nWords
if args.DUMP_WORDS: if args.DUMP_WORDS:
@ -228,7 +229,7 @@ def process(args):
def mkParser(): def mkParser():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--dict', dest = 'DAWG', type = str, required = True, parser.add_argument('--dawg', dest = 'DAWG', type = str, required = True,
help = 'the .xwd file to load') help = 'the .xwd file to load')
parser.add_argument('--dump-words', dest = 'DUMP_WORDS', default = False, parser.add_argument('--dump-words', dest = 'DUMP_WORDS', default = False,
action = 'store_true', help = 'write wordlist to stdout') action = 'store_true', help = 'write wordlist to stdout')

View file

@ -34,6 +34,7 @@
#include "gsrcwrap.h" #include "gsrcwrap.h"
#include "linuxsms.h" #include "linuxsms.h"
#include "strutils.h" #include "strutils.h"
#include "dbgutil.h"
typedef struct CursesBoardState { typedef struct CursesBoardState {
CursesAppGlobals* aGlobals; CursesAppGlobals* aGlobals;
@ -590,6 +591,7 @@ initNoDraw( CursesBoardState* cbState, sqlite3_int64 rowid,
cGlobals->cp.robotThinkMax = params->robotThinkMax; cGlobals->cp.robotThinkMax = params->robotThinkMax;
cGlobals->cp.robotTradePct = params->robotTradePct; cGlobals->cp.robotTradePct = params->robotTradePct;
#endif #endif
cGlobals->cp.makePhonyPct = params->makePhonyPct;
if ( linuxOpenGame( cGlobals, &result->procs, returnAddr ) ) { if ( linuxOpenGame( cGlobals, &result->procs, returnAddr ) ) {
result = ref( result ); result = ref( result );
@ -640,16 +642,21 @@ findOrOpen( CursesBoardState* cbState, sqlite3_int64 rowid,
return result; return result;
} }
XP_U16 bool
cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, XP_U16 expectSeed,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ) const XP_U8* buf, XP_U16 len, const CommsAddrRec* from )
{ {
LOG_FUNC(); LOG_FUNC();
CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid, NULL, NULL ); CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid, NULL, NULL );
CommonGlobals* cGlobals = &bGlobals->cGlobals; CommonGlobals* cGlobals = &bGlobals->cGlobals;
gameGotBuf( cGlobals, XP_TRUE, buf, len, from );
XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms );
return seed; bool success = 0 == expectSeed || seed == expectSeed;
if ( success ) {
gameGotBuf( cGlobals, XP_TRUE, buf, len, from );
} else {
XP_LOGFF( "msg for seed %d but I opened %d", expectSeed, seed );
}
return success;
} }
void void
@ -663,7 +670,11 @@ cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
getRowsForGameID( params->pDb, gameID, rowids, &nRows ); getRowsForGameID( params->pDb, gameID, rowids, &nRows );
XP_LOGF( "%s(): found %d rows for gameID %d", __func__, nRows, gameID ); XP_LOGF( "%s(): found %d rows for gameID %d", __func__, nRows, gameID );
for ( int ii = 0; ii < nRows; ++ii ) { for ( int ii = 0; ii < nRows; ++ii ) {
(void)cb_feedRow( cbState, rowids[ii], buf, len, from ); #ifdef DEBUG
bool success =
#endif
cb_feedRow( cbState, rowids[ii], 0, buf, len, from );
XP_ASSERT( success );
} }
} }
@ -802,13 +813,21 @@ curses_util_turnChanged( XW_UtilCtxt* uc, XP_S16 XP_UNUSED_DBG(newTurn) )
#endif #endif
static void static void
curses_util_notifyIllegalWords( XW_UtilCtxt* uc, curses_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi,
BadWordInfo* XP_UNUSED(bwi), XP_U16 player, XP_Bool turnLost )
XP_U16 XP_UNUSED(player),
XP_Bool XP_UNUSED(turnLost) )
{ {
gchar* strs = g_strjoinv( "\", \"", (gchar**)bwi->words );
gchar* msg = g_strdup_printf( "Player %d played bad word[s]: \"%s\". "
"Turn lost: %s", player, strs, boolToStr(turnLost) );
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
ca_informf( bGlobals->boardWin, "%s() not implemented", __func__ ); if ( !!bGlobals->boardWin ) {
ca_inform( bGlobals->boardWin, msg );
} else {
XP_LOGFF( "msg: %s", msg );
}
g_free( strs );
g_free( msg );
} /* curses_util_notifyIllegalWord */ } /* curses_util_notifyIllegalWord */
/* this needs to change!!! */ /* this needs to change!!! */
@ -1042,6 +1061,13 @@ curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
} }
#endif #endif
static void
curses_util_informWordBlocked( XW_UtilCtxt* XP_UNUSED(uc),
const XP_UCHAR* XP_UNUSED_DBG(word),
const XP_UCHAR* XP_UNUSED_DBG(dict) )
{
XP_LOGFF( "(word=%s, dict=%s)", word, dict );
}
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
static XWStreamCtxt* static XWStreamCtxt*
@ -1113,6 +1139,7 @@ setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util )
#ifdef XWFEATURE_BOARDWORDS #ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld); SET_PROC(cellSquareHeld);
#endif #endif
SET_PROC(informWordBlocked);
#ifdef XWFEATURE_SEARCHLIMIT #ifdef XWFEATURE_SEARCHLIMIT
SET_PROC(getTraySearchLimits); SET_PROC(getTraySearchLimits);
@ -1122,7 +1149,7 @@ setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util )
#endif #endif
#undef SET_PROC #undef SET_PROC
assertUtilCallbacksSet( util ); assertTableFull( util->vtable, sizeof(*util->vtable), "curses util" );
} /* setupCursesUtilCallbacks */ } /* setupCursesUtilCallbacks */
static bool static bool
@ -1189,10 +1216,12 @@ inviteList( CommonGlobals* cGlobals, CommsAddrRec* addr, GSList* invitees,
if ( haveAddressees ) { if ( haveAddressees ) {
LaunchParams* params = cGlobals->params; LaunchParams* params = cGlobals->params;
for ( int ii = 0; ii < g_slist_length(invitees); ++ii ) { for ( int ii = 0; ii < g_slist_length(invitees); ++ii ) {
const XP_U16 nPlayers = 1; const XP_U16 nPlayersH = params->connInfo.inviteeCounts[ii];
gint forceChannel = ii + 1; const gint forceChannel = ii + 1;
XP_LOGFF( "using nPlayersH of %d, forceChannel of %d for guest device %d",
nPlayersH, forceChannel, ii );
NetLaunchInfo nli = {0}; NetLaunchInfo nli = {0};
nli_init( &nli, cGlobals->gi, addr, nPlayers, forceChannel ); nli_init( &nli, cGlobals->gi, addr, nPlayersH, forceChannel );
if ( useRelay ) { if ( useRelay ) {
uint64_t inviteeRelayID = (uint64_t)g_slist_nth_data( invitees, ii ); uint64_t inviteeRelayID = (uint64_t)g_slist_nth_data( invitees, ii );
relaycon_invite( params, (XP_U32)inviteeRelayID, NULL, &nli ); relaycon_invite( params, (XP_U32)inviteeRelayID, NULL, &nli );
@ -1217,8 +1246,8 @@ handleInvite( void* closure, int XP_UNUSED(key) )
XP_ASSERT( comms ); XP_ASSERT( comms );
comms_getAddr( comms, &addr ); comms_getAddr( comms, &addr );
XP_U16 nPlayers = 1;
gint forceChannel = 1; gint forceChannel = 1;
const XP_U16 nPlayers = params->connInfo.inviteeCounts[forceChannel-1];
NetLaunchInfo nli = {0}; NetLaunchInfo nli = {0};
nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel ); nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel );

View file

@ -44,8 +44,9 @@ bool cb_new( CursesBoardState* cbState, const cb_dims* dims );
void cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli, void cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
const CommsAddrRec* returnAddr, const cb_dims* dims ); const CommsAddrRec* returnAddr, const cb_dims* dims );
XP_U16 cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, bool cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ); XP_U16 expectSeed, const XP_U8* buf, XP_U16 len,
const CommsAddrRec* from );
void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID, void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ); const XP_U8* buf, XP_U16 len, const CommsAddrRec* from );
void cb_closeAll( CursesBoardState* cbState ); void cb_closeAll( CursesBoardState* cbState );

View file

@ -1201,9 +1201,7 @@ cursesGotBuf( void* closure, const CommsAddrRec* addr,
rowidFromToken( XP_NTOHL( clientToken ), &rowid, &gotSeed ); rowidFromToken( XP_NTOHL( clientToken ), &rowid, &gotSeed );
/* Figure out if the device is live, or we need to open the game */ /* Figure out if the device is live, or we need to open the game */
XP_U16 seed = cb_feedRow( aGlobals->cbState, rowid, buf, len, addr ); cb_feedRow( aGlobals->cbState, rowid, gotSeed, buf, len, addr );
XP_ASSERT( seed == 0 || gotSeed == seed );
XP_USE( seed );
/* if ( seed == comms_getChannelSeed( cGlobals->game.comms ) ) { */ /* if ( seed == comms_getChannelSeed( cGlobals->game.comms ) ) { */
/* gameGotBuf( cGlobals, XP_TRUE, buf, len, addr ); */ /* gameGotBuf( cGlobals, XP_TRUE, buf, len, addr ); */

View file

@ -23,28 +23,135 @@
#include "gtkdraw.h" #include "gtkdraw.h"
#include "linuxutl.h" #include "linuxutl.h"
#include "main.h" #include "main.h"
#include "dbgutil.h"
#define SNAP_WIDTH 150 #define SNAP_WIDTH 150
#define SNAP_HEIGHT 150 #define SNAP_HEIGHT 150
#define KEY_DB_VERSION "dbvers"
#define VERS_0_TO_1 \
"nPending INT" \
",role INT" \
",dictlang INT" \
",scores TEXT" \
static XP_Bool getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, static XP_Bool getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf,
int* len ); int* len );
static bool db_fetchInt( sqlite3* pDb, const gchar* key, int32_t* resultP );
static void db_storeInt( sqlite3* pDb, const gchar* key, int32_t val );
static void createTables( sqlite3* pDb );
static bool gamesTableExists( sqlite3* pDb );
static void upgradeTables( sqlite3* pDb, int32_t oldVersion );
static void execNoResult( sqlite3* pDb, const gchar* query, bool errOK );
#ifdef DEBUG #ifdef DEBUG
static char* sqliteErr2str( int err ); static char* sqliteErr2str( int err );
#endif #endif
static void assertPrintResult( sqlite3* pDb, int result, int expect ); static void assertPrintResult( sqlite3* pDb, int result, int expect );
/* Versioning:
*
* Version 0 is defined, and not having a version code means you're 0. For
* each subsequent version there needs to be a recipe for upgrading, whether
* it's adding new fields or whatever.
*/
#define CUR_DB_VERSION 1
sqlite3* sqlite3*
openGamesDB( const char* dbName ) openGamesDB( const char* dbName )
{ {
int result = sqlite3_initialize(); #ifdef DEBUG
int result =
#endif
sqlite3_initialize();
XP_ASSERT( SQLITE_OK == result ); XP_ASSERT( SQLITE_OK == result );
sqlite3* pDb = NULL; sqlite3* pDb = NULL;
result = sqlite3_open( dbName, &pDb ); #ifdef DEBUG
result =
#endif
sqlite3_open( dbName, &pDb );
XP_ASSERT( SQLITE_OK == result ); XP_ASSERT( SQLITE_OK == result );
if ( gamesTableExists( pDb ) ) {
int32_t oldVersion;
if ( !db_fetchInt( pDb, KEY_DB_VERSION, &oldVersion ) ) {
oldVersion = 0;
XP_LOGFF( "no version found; assuming %d", oldVersion );
}
if ( oldVersion < CUR_DB_VERSION ) {
upgradeTables( pDb, oldVersion );
}
} else {
createTables( pDb );
}
return pDb;
}
static void
upgradeTables( sqlite3* pDb, int32_t oldVersion )
{
gchar* newCols = NULL;
switch ( oldVersion ) {
case 0:
XP_ASSERT( 1 == CUR_DB_VERSION );
newCols = VERS_0_TO_1;
break;
default:
XP_ASSERT(0);
break;
}
if ( !!newCols ) {
gchar** strs = g_strsplit( newCols, ",", -1 );
for ( int ii = 0; !!strs[ii]; ++ii ) {
gchar* str = strs[ii];
if ( 0 < strlen(str) ) {
gchar* query = g_strdup_printf( "ALTER TABLE games ADD COLUMN %s", strs[ii] );
XP_LOGFF( "query: \"%s\"", query );
execNoResult( pDb, query, true );
g_free( query );
}
}
g_strfreev( strs );
db_storeInt( pDb, KEY_DB_VERSION, CUR_DB_VERSION );
}
}
static bool
gamesTableExists( sqlite3* pDb )
{
const gchar* query = "SELECT COUNT(*) FROM sqlite_master "
"WHERE type = 'table' AND name = 'games'";
sqlite3_stmt *ppStmt;
int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
assertPrintResult( pDb, result, SQLITE_OK );
result = sqlite3_step( ppStmt );
XP_ASSERT( SQLITE_ROW == result );
bool exists = 1 == sqlite3_column_int( ppStmt, 0 );
sqlite3_finalize( ppStmt );
LOG_RETURNF( "%s", boolToStr(exists) );
return exists;
}
static void
createTables( sqlite3* pDb )
{
/* This can never change! Versioning counts on it. */
const char* createValuesStr =
"CREATE TABLE pairs ( key TEXT UNIQUE, value TEXT )";
#ifdef DEBUG
int result =
#endif
sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL );
XP_LOGFF( "sqlite3_exec(%s)=>%d", createValuesStr, result );
const char* createGamesStr = const char* createGamesStr =
"CREATE TABLE games ( " "CREATE TABLE games ( "
"rowid INTEGER PRIMARY KEY AUTOINCREMENT" "rowid INTEGER PRIMARY KEY AUTOINCREMENT"
@ -59,25 +166,17 @@ openGamesDB( const char* dbName )
",local INT(1)" ",local INT(1)"
",nmoves INT" ",nmoves INT"
",seed INT" ",seed INT"
",nPending INT"
",role INT"
",dictlang INT"
",gameid INT" ",gameid INT"
",ntotal INT(2)" ",ntotal INT(2)"
",nmissing INT(2)" ",nmissing INT(2)"
",lastMoveTime INT" ",lastMoveTime INT"
",scores TEXT"
",dupTimerExpires INT" ",dupTimerExpires INT"
","VERS_0_TO_1
// ",dupTimerExpires INT"
")"; ")";
result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); (void)sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL );
const char* createValuesStr = db_storeInt( pDb, KEY_DB_VERSION, CUR_DB_VERSION );
"CREATE TABLE pairs ( key TEXT UNIQUE,value TEXT )";
result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL );
XP_LOGFF( "sqlite3_exec=>%d", result );
XP_USE( result );
return pDb;
} }
void void
@ -558,30 +657,46 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
XP_ASSERT( !!pDb ); XP_ASSERT( !!pDb );
char query[256]; char query[256];
snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid ); snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid );
sqlite3_stmt* ppStmt; execNoResult( pDb, query, false );
int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
assertPrintResult( pDb, result, SQLITE_OK );
result = sqlite3_step( ppStmt );
assertPrintResult( pDb, result, SQLITE_DONE );
XP_USE( result );
sqlite3_finalize( ppStmt );
} }
void void
db_store( sqlite3* pDb, const gchar* key, const gchar* value ) db_store( sqlite3* pDb, const gchar* key, const gchar* value )
{ {
XP_ASSERT( !!pDb ); XP_ASSERT( !!pDb );
gchar* buf = gchar* query =
g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
key, value ); key, value );
sqlite3_stmt *ppStmt; execNoResult( pDb, query, false );
int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); g_free( query );
assertPrintResult( pDb, result, SQLITE_OK ); }
result = sqlite3_step( ppStmt );
assertPrintResult( pDb, result, SQLITE_DONE ); static bool
XP_USE( result ); db_fetchInt( sqlite3* pDb, const gchar* key, int32_t* resultP )
sqlite3_finalize( ppStmt ); {
g_free( buf ); gint buflen = 16;
gchar buf[buflen];
FetchResult fr = db_fetch( pDb, key, buf, &buflen );
bool gotIt = SUCCESS == fr;
if ( gotIt ) {
buf[buflen] = '\0';
sscanf( buf, "%x", resultP );
}
return gotIt;
}
static void
db_storeInt( sqlite3* pDb, const gchar* key, int32_t val )
{
gchar buf[32];
snprintf( buf, VSIZE(buf), "%x", val );
db_store( pDb, key, buf );
#ifdef DEBUG
int32_t tmp;
bool worked = db_fetchInt( pDb, key, &tmp );
XP_ASSERT( worked && tmp == val );
#endif
} }
FetchResult FetchResult
@ -632,13 +747,7 @@ db_remove( sqlite3* pDb, const gchar* key )
XP_ASSERT( !!pDb ); XP_ASSERT( !!pDb );
char query[256]; char query[256];
snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key ); snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key );
sqlite3_stmt *ppStmt; execNoResult( pDb, query, false );
int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
assertPrintResult( pDb, result, SQLITE_OK );
result = sqlite3_step( ppStmt );
assertPrintResult( pDb, result, SQLITE_DONE );
XP_USE( result );
sqlite3_finalize( ppStmt );
} }
static XP_Bool static XP_Bool
@ -656,6 +765,21 @@ getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int *len )
return success; return success;
} }
static void
execNoResult( sqlite3* pDb, const gchar* query, bool errOk )
{
sqlite3_stmt *ppStmt;
int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
if ( ! errOk ) {
assertPrintResult( pDb, result, SQLITE_OK );
}
result = sqlite3_step( ppStmt );
if ( ! errOk ) {
assertPrintResult( pDb, result, SQLITE_DONE );
}
sqlite3_finalize( ppStmt );
}
#ifdef DEBUG #ifdef DEBUG
# define CASESTR(c) case c: return #c # define CASESTR(c) case c: return #c
static char* static char*
@ -704,7 +828,7 @@ assertPrintResult( sqlite3* pDb, int XP_UNUSED_DBG(result), int expect )
int code = sqlite3_errcode( pDb ); int code = sqlite3_errcode( pDb );
XP_ASSERT( code == result ); /* do I need to pass it? */ XP_ASSERT( code == result ); /* do I need to pass it? */
if ( code != expect ) { if ( code != expect ) {
XP_LOGFF( "sqlite3 error: %s", sqlite3_errmsg( pDb ) ); XP_LOGFF( "sqlite3 error: %d (%s)", code, sqlite3_errmsg( pDb ) );
XP_ASSERT(0); XP_ASSERT(0);
} }
} }

View file

@ -48,7 +48,7 @@ gtkask( GtkWidget* parent, const gchar *message, GtkButtonsType buttons,
gint gint
gtkask_timeout( GtkWidget* parent, const gchar* message, gtkask_timeout( GtkWidget* parent, const gchar* message,
GtkButtonsType buttons, const AskPair* buttxts, GtkButtonsType buttons, const AskPair* buttxts,
XP_U16 timeout ) uint32_t timeoutMS )
{ {
guint src = 0; guint src = 0;
GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)parent, GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)parent,
@ -56,9 +56,9 @@ gtkask_timeout( GtkWidget* parent, const gchar *message,
GTK_DIALOG_MODAL, GTK_DIALOG_MODAL,
buttons, "%s", message ); buttons, "%s", message );
if ( timeout > 0 ) { if ( timeoutMS > 0 ) {
XP_LOGF( "%s(%s)", __func__, message ); /* log since times out... */ XP_LOGF( "%s(\"%s\")", __func__, message ); /* log since times out... */
src = g_timeout_add( timeout, timer_func, dlg ); src = g_timeout_add( timeoutMS, timer_func, dlg );
} }
while ( !!buttxts && !!buttxts->txt ) { while ( !!buttxts && !!buttxts->txt ) {

View file

@ -38,7 +38,7 @@ gint gtkask( GtkWidget* parent, const gchar *message,
GtkButtonsType buttons, const AskPair* buttxts ); GtkButtonsType buttons, const AskPair* buttxts );
gint gtkask_timeout( GtkWidget* parent, const gchar *message, gint gtkask_timeout( GtkWidget* parent, const gchar *message,
GtkButtonsType buttons, const AskPair* buttxts, GtkButtonsType buttons, const AskPair* buttxts,
XP_U16 timeout ); uint32_t timeoutMS );
#endif #endif
#endif /* PLATFORM_GTK */ #endif /* PLATFORM_GTK */

View file

@ -583,7 +583,8 @@ createOrLoadObjects( GtkGameGlobals* globals )
TransportProcs procs; TransportProcs procs;
setTransportProcs( &procs, globals ); setTransportProcs( &procs, globals );
if ( linuxOpenGame( cGlobals, &procs, NULL ) ) {
if ( linuxOpenGame( cGlobals, &procs, &cGlobals->addr ) ) {
if ( !params->fileName && !!params->dbName ) { if ( !params->fileName && !!params->dbName ) {
XP_UCHAR buf[64]; XP_UCHAR buf[64];
@ -1869,7 +1870,7 @@ gtk_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player,
XP_UCHAR* name = cGlobals->gi->players[player].name; XP_UCHAR* name = cGlobals->gi->players[player].name;
XP_ASSERT( !!name ); XP_ASSERT( !!name );
sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", sprintf( buf, "Player %d (%s) played illegal word[s] \"%s\"; loses turn.",
player+1, name, strs ); player+1, name, strs );
if ( cGlobals->params->skipWarnings ) { if ( cGlobals->params->skipWarnings ) {
@ -1961,9 +1962,10 @@ gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc),
static void static void
gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus )
{ {
LOG_FUNC(); GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
XP_USE( uc ); gchar* msg = g_strdup_printf( "bonusSquareHeld(bonus=%d)", bonus );
XP_USE( bonus ); gtkask_timeout( globals->window, msg, GTK_BUTTONS_OK, NULL, 1000 );
g_free( msg );
} }
static void static void
@ -1987,12 +1989,24 @@ gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player )
static void static void
gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
{ {
XP_USE( uc ); GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
catOnClose( words, NULL ); const XP_U8* bytes = stream_getPtr( words );
fprintf( stderr, "\n" ); gchar* msg = g_strdup_printf( "words for lookup:\n%s",
(XP_UCHAR*)bytes );
gtktell( globals->window, msg );
g_free( msg );
} }
#endif #endif
static void
gtk_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word, const XP_UCHAR* dict )
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
gchar* msg = g_strdup_printf( "Word \"%s\" not found in %s", word, dict );
gtkUserError( globals, msg );
g_free( msg );
}
static void static void
gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id )
{ {
@ -2225,10 +2239,10 @@ setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util )
#ifdef XWFEATURE_BOARDWORDS #ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld); SET_PROC(cellSquareHeld);
#endif #endif
SET_PROC(informWordBlocked);
#undef SET_PROC #undef SET_PROC
assertUtilCallbacksSet( util ); assertTableFull( util->vtable, sizeof(*util->vtable), "gtk util" );
} /* setupGtkUtilCallbacks */ } /* setupGtkUtilCallbacks */
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
@ -2358,6 +2372,7 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params,
cGlobals->cp.robotThinkMax = params->robotThinkMax; cGlobals->cp.robotThinkMax = params->robotThinkMax;
cGlobals->cp.robotTradePct = params->robotTradePct; cGlobals->cp.robotTradePct = params->robotTradePct;
#endif #endif
cGlobals->cp.makePhonyPct = params->makePhonyPct;
#ifdef XWFEATURE_CROSSHAIRS #ifdef XWFEATURE_CROSSHAIRS
cGlobals->cp.hideCrosshairs = params->hideCrosshairs; cGlobals->cp.hideCrosshairs = params->hideCrosshairs;
#endif #endif

View file

@ -779,7 +779,7 @@ gtkDrawTileImpl( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP,
formatRect.width -= 6; formatRect.width -= 6;
if ( notEmpty ) { if ( notEmpty ) {
if ( !!bitmaps ) { if ( !!bitmaps && !!bitmaps->bmps[1] ) {
drawBitmapFromLBS( dctx, bitmaps->bmps[1], &insetR ); drawBitmapFromLBS( dctx, bitmaps->bmps[1], &insetR );
} else if ( !!textP ) { } else if ( !!textP ) {
if ( *textP != LETTER_NONE ) { /* blank */ if ( *textP != LETTER_NONE ) { /* blank */

View file

@ -24,18 +24,21 @@
#include "gtkask.h" #include "gtkask.h"
static void static void
set_bool_and_quit( GtkWidget* XP_UNUSED(widget), gpointer closure ) set_bool_and_quit( GtkWidget* widget, gpointer closure )
{ {
XP_Bool* whichSet = (XP_Bool*)closure; XP_Bool* whichSet = (XP_Bool*)closure;
*whichSet = XP_TRUE; *whichSet = XP_TRUE;
gtk_main_quit();
GtkWidget* dialog = gtk_widget_get_toplevel( widget );
gtk_dialog_response( GTK_DIALOG(dialog), 1000 );
} /* button_event */ } /* button_event */
#ifdef FEATURE_TRAY_EDIT #ifdef FEATURE_TRAY_EDIT
static void static void
abort_button_event( GtkWidget* XP_UNUSED(widget), gpointer XP_UNUSED(closure) ) abort_button_event( GtkWidget* widget, gpointer XP_UNUSED(closure) )
{ {
gtk_main_quit(); GtkWidget* dialog = gtk_widget_get_toplevel( widget );
gtk_dialog_response( GTK_DIALOG(dialog), 1000 );
} /* abort_button_event */ } /* abort_button_event */
#endif #endif
@ -51,7 +54,6 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name,
GtkWidget* vbox; GtkWidget* vbox;
GtkWidget* hbox = NULL; GtkWidget* hbox = NULL;
char* txt; char* txt;
XP_S16 ii;
GtkWidget* button; GtkWidget* button;
XP_UCHAR buf[64]; XP_UCHAR buf[64];
XP_Bool backedUp = XP_FALSE; XP_Bool backedUp = XP_FALSE;
@ -60,7 +62,7 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name,
vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
for ( ii = 0; ii < nTiles; ++ii ) { for ( int ii = 0; ii < nTiles; ++ii ) {
if ( ii % BUTTONS_PER_ROW == 0 ) { if ( ii % BUTTONS_PER_ROW == 0 ) {
hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 );
@ -114,7 +116,7 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name,
char curTilesBuf[64]; char curTilesBuf[64];
int len = snprintf( curTilesBuf, sizeof(curTilesBuf), "%s", int len = snprintf( curTilesBuf, sizeof(curTilesBuf), "%s",
"Tiles so far: " ); "Tiles so far: " );
for ( ii = 0; ii < curPick->nTiles; ++ii ) { for ( int ii = 0; ii < curPick->nTiles; ++ii ) {
Tile tile = curPick->tiles[ii]; Tile tile = curPick->tiles[ii];
len += snprintf( &curTilesBuf[len], sizeof(curTilesBuf) - len, "%s ", len += snprintf( &curTilesBuf[len], sizeof(curTilesBuf) - len, "%s ",
texts[tile] ); texts[tile] );
@ -129,24 +131,25 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name,
gtk_dialog_add_action_widget( GTK_DIALOG(dialog), vbox, 0 ); gtk_dialog_add_action_widget( GTK_DIALOG(dialog), vbox, 0 );
gtk_widget_show_all( dialog ); gtk_widget_show_all( dialog );
gtk_dialog_run( GTK_DIALOG( dialog ) ); // gint dlgResult =
(void)gtk_dialog_run( GTK_DIALOG( dialog ) );
gtk_widget_destroy( dialog ); gtk_widget_destroy( dialog );
XP_S16 result;
if ( backedUp ) { if ( backedUp ) {
ii = PICKER_BACKUP; result = PICKER_BACKUP;
} else { } else {
for ( ii = 0; ii < nTiles; ++ii ) { result = PICKER_PICKALL;
for ( int ii = 0; ii < nTiles; ++ii ) {
if ( results[ii] ) { if ( results[ii] ) {
result = ii;
break; break;
} }
} }
if ( ii == nTiles ) {
ii = PICKER_PICKALL;
}
} }
return ii; return result;
} /* gtkletterask */ } /* gtkletterask */
#endif /* PLATFORM_GTK */ #endif /* PLATFORM_GTK */

View file

@ -218,7 +218,7 @@ addPhoniesCombo( GtkNewGameState* state, GtkWidget* parent )
FALSE, TRUE, 0 ); FALSE, TRUE, 0 );
GtkWidget* phoniesCombo = gtk_combo_box_text_new(); GtkWidget* phoniesCombo = gtk_combo_box_text_new();
const char* ptxts[] = { "IGNORE", "WARN", "DISALLOW" }; const char* ptxts[] = { "IGNORE", "WARN", "LOSE TURN", "BLOCK" };
for ( int ii = 0; ii < VSIZE(ptxts); ++ii ) { for ( int ii = 0; ii < VSIZE(ptxts); ++ii ) {
gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(phoniesCombo), gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(phoniesCombo),
@ -227,6 +227,8 @@ addPhoniesCombo( GtkNewGameState* state, GtkWidget* parent )
g_signal_connect( phoniesCombo, "changed", g_signal_connect( phoniesCombo, "changed",
G_CALLBACK(phonies_combo_changed), state ); G_CALLBACK(phonies_combo_changed), state );
XWPhoniesChoice startChoice = state->globals->cGlobals.params->pgi.phoniesAction;
gtk_combo_box_set_active( GTK_COMBO_BOX(phoniesCombo), startChoice );
gtk_widget_show( phoniesCombo ); gtk_widget_show( phoniesCombo );
gtk_box_pack_start( GTK_BOX(hbox), phoniesCombo, FALSE, TRUE, 0 ); gtk_box_pack_start( GTK_BOX(hbox), phoniesCombo, FALSE, TRUE, 0 );
gtk_widget_show( hbox ); gtk_widget_show( hbox );

View file

@ -329,7 +329,7 @@ linuxOpenGame( CommonGlobals* cGlobals, const TransportProcs* procs,
cGlobals->gi->allowHintRect = params->allowHintRect; cGlobals->gi->allowHintRect = params->allowHintRect;
#endif #endif
if ( params->needsNewGame ) { if ( params->needsNewGame && !opened ) {
XP_ASSERT(0); XP_ASSERT(0);
// new_game_impl( globals, XP_FALSE ); // new_game_impl( globals, XP_FALSE );
} }
@ -881,6 +881,7 @@ typedef enum {
,CMD_INVITEE_SMSNUMBER ,CMD_INVITEE_SMSNUMBER
,CMD_SMSPORT ,CMD_SMSPORT
#endif #endif
,CMD_INVITEE_COUNTS
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
,CMD_ROOMNAME ,CMD_ROOMNAME
,CMD_ADVERTISEROOM ,CMD_ADVERTISEROOM
@ -896,6 +897,7 @@ typedef enum {
,CMD_SLOWROBOT ,CMD_SLOWROBOT
,CMD_TRADEPCT ,CMD_TRADEPCT
#endif #endif
,CMD_MAKE_PHONY_PCT
#ifdef USE_GLIBLOOP /* just because hard to implement otherwise */ #ifdef USE_GLIBLOOP /* just because hard to implement otherwise */
,CMD_UNDOPCT ,CMD_UNDOPCT
#endif #endif
@ -1013,12 +1015,15 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_INVITEE_SMSNUMBER, true, "invitee-sms-number", "number to send any invitation to" } ,{ CMD_INVITEE_SMSNUMBER, true, "invitee-sms-number", "number to send any invitation to" }
,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" } ,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" }
#endif #endif
,{ CMD_INVITEE_COUNTS, true, "invitee-counts",
"When invitations sent, how many on each device? e.g. \"1:2\" for a "
"three-dev game with two players on second guest" }
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
,{ CMD_ROOMNAME, true, "room", "name of room on relay" } ,{ CMD_ROOMNAME, true, "room", "name of room on relay" }
,{ CMD_ADVERTISEROOM, false, "make-public", "make room public on relay" } ,{ CMD_ADVERTISEROOM, false, "make-public", "make room public on relay" }
,{ CMD_JOINADVERTISED, false, "join-public", "look for a public room" } ,{ CMD_JOINADVERTISED, false, "join-public", "look for a public room" }
,{ CMD_PHONIES, true, "phonies", ,{ CMD_PHONIES, true, "phonies",
"ignore (0, default), warn (1) or lose turn (2)" } "ignore (0, default), warn (1), lose turn (2), or refuse to commit (3)" }
,{ CMD_BONUSFILE, true, "bonus-file", ,{ CMD_BONUSFILE, true, "bonus-file",
"provides bonus info: . + * ^ and ! are legal" } "provides bonus info: . + * ^ and ! are legal" }
,{ CMD_INVITEE_RELAYID, true, "invitee-relayid", "relayID to send any invitation to" } ,{ CMD_INVITEE_RELAYID, true, "invitee-relayid", "relayID to send any invitation to" }
@ -1030,6 +1035,8 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_SLOWROBOT, true, "slow-robot", "make robot slower to test network" } ,{ CMD_SLOWROBOT, true, "slow-robot", "make robot slower to test network" }
,{ CMD_TRADEPCT, true, "trade-pct", "what pct of the time should robot trade" } ,{ CMD_TRADEPCT, true, "trade-pct", "what pct of the time should robot trade" }
#endif #endif
,{ CMD_MAKE_PHONY_PCT, true, "make-phony-pct",
"what pct of the time should robot play a bad word" }
#ifdef USE_GLIBLOOP #ifdef USE_GLIBLOOP
,{ CMD_UNDOPCT, true, "undo-pct", ,{ CMD_UNDOPCT, true, "undo-pct",
"each second, what are the odds of doing an undo" } "each second, what are the odds of doing an undo" }
@ -2388,20 +2395,6 @@ setupLinuxUtilCallbacks( XW_UtilCtxt* util )
#undef SET_PROC #undef SET_PROC
} }
void
assertUtilCallbacksSet( XW_UtilCtxt* util )
{
XWStreamCtxt* (**proc)(XW_UtilCtxt*, XP_PlayerAddr ) =
&util->vtable->m_util_makeStreamFromAddr;
for ( int ii = 0; ii < sizeof(*util->vtable)/sizeof(*proc); ++ii ) {
if ( !*proc ) {
XP_LOGF( "%s(): null ptr at index %d", __func__, ii );
XP_ASSERT( 0 );
}
++proc;
}
}
void void
assertDrawCallbacksSet( const DrawCtxVTable* vtable ) assertDrawCallbacksSet( const DrawCtxVTable* vtable )
{ {
@ -2548,6 +2541,9 @@ main( int argc, char** argv )
initParams( &mainParams ); initParams( &mainParams );
/* defaults */ /* defaults */
for ( int ii = 0; ii < VSIZE(mainParams.connInfo.inviteeCounts); ++ii ) {
mainParams.connInfo.inviteeCounts[ii] = 1;
}
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT; mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT;
mainParams.connInfo.relay.relayName = "localhost"; mainParams.connInfo.relay.relayName = "localhost";
@ -2748,6 +2744,16 @@ main( int argc, char** argv )
g_slist_append( mainParams.connInfo.sms.inviteePhones, optarg ); g_slist_append( mainParams.connInfo.sms.inviteePhones, optarg );
addr_addType( &mainParams.addr, COMMS_CONN_SMS ); addr_addType( &mainParams.addr, COMMS_CONN_SMS );
break; break;
case CMD_INVITEE_COUNTS: {
gchar** strs = g_strsplit( optarg, ":", -1 );
for ( int ii = 0;
!!strs[ii] && ii < VSIZE(mainParams.connInfo.inviteeCounts);
++ii ) {
mainParams.connInfo.inviteeCounts[ii] = atoi(strs[ii]);
}
g_strfreev( strs );
}
break;
case CMD_SMSPORT: case CMD_SMSPORT:
mainParams.connInfo.sms.port = atoi(optarg); mainParams.connInfo.sms.port = atoi(optarg);
addr_addType( &mainParams.addr, COMMS_CONN_SMS ); addr_addType( &mainParams.addr, COMMS_CONN_SMS );
@ -2837,8 +2843,11 @@ main( int argc, char** argv )
case 2: case 2:
mainParams.pgi.phoniesAction = PHONIES_DISALLOW; mainParams.pgi.phoniesAction = PHONIES_DISALLOW;
break; break;
case 3:
mainParams.pgi.phoniesAction = PHONIES_BLOCK;
break;
default: default:
usage( argv[0], "phonies takes 0 or 1 or 2" ); usage( argv[0], "phonies takes 0 or 1 or 2 or 3" );
} }
break; break;
case CMD_BONUSFILE: case CMD_BONUSFILE:
@ -2955,6 +2964,13 @@ main( int argc, char** argv )
usage(argv[0], "must be 0 <= n <= 100" ); usage(argv[0], "must be 0 <= n <= 100" );
} }
break; break;
case CMD_MAKE_PHONY_PCT:
mainParams.makePhonyPct = atoi( optarg );
if ( mainParams.makePhonyPct < 0 || mainParams.makePhonyPct > 100 ) {
usage(argv[0], "must be 0 <= n <= 100" );
}
break;
#endif #endif
#ifdef USE_GLIBLOOP #ifdef USE_GLIBLOOP

View file

@ -226,6 +226,20 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
return nRead; return nRead;
} /* decodeAndDelete */ } /* decodeAndDelete */
static void
nliFromData( LaunchParams* params, const SMSMsgLoc* msg, NetLaunchInfo* nliOut )
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );
stream_putBytes( stream, msg->data, msg->len );
#ifdef DEBUG
XP_Bool success =
#endif
nli_makeFromStream( nliOut, stream );
XP_ASSERT( success );
stream_destroy( stream );
}
static void static void
parseAndDispatch( LaunchParams* params, uint8_t* buf, int len, parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
CommsAddrRec* addr ) CommsAddrRec* addr )
@ -244,9 +258,12 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
msg->gameID, msg->gameID,
msg->data, msg->len ); msg->data, msg->len );
break; break;
case INVITE: case INVITE: {
NetLaunchInfo nli = {0};
nliFromData( params, msg, &nli );
(*storage->procs->inviteReceived)( storage->procClosure, (*storage->procs->inviteReceived)( storage->procClosure,
(NetLaunchInfo*)msg->data, addr ); &nli, addr );
}
break; break;
default: default:
XP_ASSERT(0); /* implement me!! */ XP_ASSERT(0); /* implement me!! */
@ -291,13 +308,21 @@ linux_sms_invite( LaunchParams* params, const NetLaunchInfo* nli,
LOG_FUNC(); LOG_FUNC();
LinSMSData* storage = getStorage( params ); LinSMSData* storage = getStorage( params );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr );
nli_saveToStream( nli, stream );
const XP_U8* ptr = stream_getPtr( stream );
XP_U16 len = stream_getSize( stream );
XP_U16 waitSecs; XP_U16 waitSecs;
const XP_Bool forceOld = XP_TRUE; /* Send NOW in case test app kills us */
SMSMsgArray* arr SMSMsgArray* arr
= smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, nli, = smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, ptr,
sizeof(*nli), toPhone, toPort, XP_FALSE, len, toPhone, toPort, forceOld, &waitSecs );
&waitSecs ); XP_ASSERT( !!arr || !forceOld );
sendOrRetry( params, arr, INVITE, waitSecs, toPhone, toPort, sendOrRetry( params, arr, INVITE, waitSecs, toPhone, toPort,
nli->gameID, "invite" ); nli->gameID, "invite" );
stream_destroy( stream );
} }
XP_S16 XP_S16

View file

@ -129,12 +129,14 @@ typedef struct LaunchParams {
XP_U16 robotThinkMin, robotThinkMax; XP_U16 robotThinkMin, robotThinkMax;
XP_U16 robotTradePct; XP_U16 robotTradePct;
#endif #endif
XP_U16 makePhonyPct;
XP_Bool commsDisableds[COMMS_CONN_NTYPES][2]; XP_Bool commsDisableds[COMMS_CONN_NTYPES][2];
DeviceRole serverRole; DeviceRole serverRole;
CommsAddrRec addr; CommsAddrRec addr;
struct { struct {
XP_U16 inviteeCounts[MAX_NUM_PLAYERS];
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
struct { struct {
char* relayName; char* relayName;

View file

@ -210,12 +210,16 @@ class Device():
def setApp(self, pct): def setApp(self, pct):
if self.app == self.args.APP_OLD and not self.app == self.args.APP_NEW: if self.app == self.args.APP_OLD and not self.app == self.args.APP_NEW:
if pct >= random.randint(0, 99): if os.path.exists(self.script) and pct >= random.randint(0, 99):
print('launch(): upgrading from ', self.app, ' to ', self.args.APP_NEW) print('launch(): upgrading {} from {} to {}' \
.format(self.devName(), self.app, self.args.APP_NEW))
self.app = self.args.APP_NEW self.app = self.args.APP_NEW
# nuke script to force regeneration # nuke script to force regeneration
os.unlink(self.script) os.unlink(self.script)
def devName(self):
return 'dev_' + str(self.indx)
def logReaderMain(self): def logReaderMain(self):
assert self and self.proc assert self and self.proc
# print('logReaderMain called; opening:', self.logPath) # print('logReaderMain called; opening:', self.logPath)
@ -418,6 +422,8 @@ def build_cmds(args):
# make one in three games public # make one in three games public
usePublic = args.ADD_RELAY and random.randint(0, 3) == 0 usePublic = args.ADD_RELAY and random.randint(0, 3) == 0
useDupeMode = random.randint(0, 100) < args.DUP_PCT useDupeMode = random.randint(0, 100) < args.DUP_PCT
if args.PHONIES == -1: phonies = GAME % 3
else: phonies = args.PHONIES
DEV = 0 DEV = 0
for NLOCALS in LOCALS: for NLOCALS in LOCALS:
DEV += 1 DEV += 1
@ -474,9 +480,15 @@ def build_cmds(args):
# it isn't a priority. # it isn't a priority.
# PARAMS += ['--seed', args.SEED] # PARAMS += ['--seed', args.SEED]
if DEV == 1: PARAMS += ['--server']
if DEV == 1 or usePublic: PARAMS += ['--force-game'] if DEV == 1 or usePublic: PARAMS += ['--force-game']
if DEV > 1: PARAMS += ['--force-channel', DEV - 1] if DEV == 1:
PARAMS += ['--server', '--phonies', phonies ]
# IFF there are any non-1 player counts, tell inviter which
if sum(LOCALS) > NDEVS:
PARAMS += ['--invitee-counts', ":".join(str(n) for n in LOCALS[1:])]
else:
PARAMS += ['--force-channel', DEV - 1]
if args.PHONY_PCT and phonies == 2: PARAMS += [ '--make-phony-pct', args.PHONY_PCT ]
if useDupeMode: PARAMS += ['--duplicate-mode'] if useDupeMode: PARAMS += ['--duplicate-mode']
if usePublic: PARAMS += ['--make-public', '--join-public'] if usePublic: PARAMS += ['--make-public', '--join-public']
@ -679,7 +691,7 @@ def mkParser():
help = 'the app we\'ll upgrade from') help = 'the app we\'ll upgrade from')
parser.add_argument('--start-pct', dest = 'START_PCT', default = 50, type = int, parser.add_argument('--start-pct', dest = 'START_PCT', default = 50, type = int,
help = 'odds of starting with the new app, 0 <= n < 100') help = 'odds of starting with the new app, 0 <= n < 100')
parser.add_argument('--upgrade-pct', dest = 'UPGRADE_PCT', default = 5, type = int, parser.add_argument('--upgrade-pct', dest = 'UPGRADE_PCT', default = 20, type = int,
help = 'odds of upgrading at any launch, 0 <= n < 100') help = 'odds of upgrading at any launch, 0 <= n < 100')
parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games') parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games')
@ -690,11 +702,16 @@ def mkParser():
parser.add_argument('--nochange-secs', dest = 'NO_CHANGE_SECS', default = 30, type = int, parser.add_argument('--nochange-secs', dest = 'NO_CHANGE_SECS', default = 30, type = int,
help = 'seconds without change after which to timeout') help = 'seconds without change after which to timeout')
parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go') parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go')
parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, help = 'send all packet twice') parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False,
help = 'send all packet twice')
parser.add_argument('--phonies', dest = 'PHONIES', default = -1, type = int,
help = '0 (ignore), 1 (warn)) or 2 (lose turn); default is pick at random')
parser.add_argument('--make-phony-pct', dest = 'PHONY_PCT', default = 20, type = int,
help = 'how often a robot should play a phony (only applies when --phonies==2')
parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true', parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true',
help = 'run games using gtk instead of ncurses') help = 'run games using gtk instead of ncurses')
parser.add_argument('--duplicate-pct', dest = 'DUP_PCT', default = 50, type = int, parser.add_argument('--dup-pct', dest = 'DUP_PCT', default = 0, type = int,
help = 'this fraction played in duplicate mode') help = 'this fraction played in duplicate mode')
# # # #