mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-07 05:24:46 +01:00
Merge branch 'android_branch' into android_translate
This commit is contained in:
commit
5786151e76
73 changed files with 1712 additions and 476 deletions
|
@ -1,6 +1,6 @@
|
|||
def INITIAL_CLIENT_VERS = 9
|
||||
def VERSION_CODE_BASE = 152
|
||||
def VERSION_NAME = '4.4.156'
|
||||
def VERSION_CODE_BASE = 154
|
||||
def VERSION_NAME = '4.4.158'
|
||||
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
|
||||
def BUILD_INFO_NAME = "build-info.txt"
|
||||
|
||||
|
@ -96,7 +96,6 @@ android {
|
|||
resValue "string", "app_name", "CrossWords"
|
||||
resValue "string", "nbs_port", "3344"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
||||
buildConfigField "String", "VARIANT_NAME", "\"Google Play Store\""
|
||||
buildConfigField "int", "VARIANT_CODE", "1"
|
||||
buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
|
||||
|
@ -112,7 +111,6 @@ android {
|
|||
resValue "string", "app_name", "CrossWords"
|
||||
resValue "string", "nbs_port", "3344"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
||||
buildConfigField "String", "VARIANT_NAME", "\"F-Droid\""
|
||||
buildConfigField "int", "VARIANT_CODE", "2"
|
||||
buildConfigField "boolean", "FOR_FDROID", "true"
|
||||
|
@ -129,7 +127,6 @@ android {
|
|||
resValue "string", "app_name", "CrossDbg"
|
||||
resValue "string", "nbs_port", "3345"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "true"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
|
||||
buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug\""
|
||||
buildConfigField "int", "VARIANT_CODE", "3"
|
||||
buildConfigField "boolean", "REPORT_LOCKS", "true"
|
||||
|
@ -148,7 +145,6 @@ android {
|
|||
resValue "string", "app_name", "CrossDbg"
|
||||
resValue "string", "nbs_port", "3345"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "true"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
|
||||
buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug NoSMS\""
|
||||
buildConfigField "int", "VARIANT_CODE", "4"
|
||||
buildConfigField "boolean", "REPORT_LOCKS", "true"
|
||||
|
@ -165,7 +161,6 @@ android {
|
|||
resValue "string", "app_name", "CrossWords"
|
||||
resValue "string", "nbs_port", "3344"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
||||
buildConfigField "String", "VARIANT_NAME", "\"FOSS\""
|
||||
buildConfigField "int", "VARIANT_CODE", "5"
|
||||
buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\""
|
||||
|
@ -230,7 +225,8 @@ android {
|
|||
debug {
|
||||
debuggable 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'
|
||||
// This doesn't work on marshmallow: duplicate permission error
|
||||
// applicationIdSuffix ".debug"
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
<activity android:name="MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="standard"
|
||||
android:launchMode="singleTask"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
@ -13,9 +13,14 @@
|
|||
</style>
|
||||
</head>
|
||||
<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">
|
||||
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
|
||||
|
@ -25,10 +30,9 @@
|
|||
|
||||
<h3>New with this release</h3>
|
||||
<ul>
|
||||
<li>Fix so game list entries collapse correctly again</li>
|
||||
<li>Fix so bringing app to front keeps the open game open</li>
|
||||
<li>Show name in scoreboard as "Not here yet" until invitee connects</li>
|
||||
<li>Include more Norwegian translations</li>
|
||||
<li>Fix timing problems when the "Phonies" setting is DISALLOW</li>
|
||||
<li>Clean up "Edit player" dialog</li>
|
||||
<li>Add support for Hungarian wordlists</li>
|
||||
</ul>
|
||||
|
||||
<p>(The full changelog
|
||||
|
@ -36,10 +40,7 @@
|
|||
|
||||
<h3>Next up</h3>
|
||||
<ul>
|
||||
<li>Improve move-via-NFC</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>
|
||||
<li>Fix sometimes allowing same player to move twice</li>
|
||||
</ul>
|
||||
|
||||
<p>Please let me know
|
||||
|
|
|
@ -32,7 +32,8 @@ public class Assert {
|
|||
assertTrue(! val);
|
||||
}
|
||||
|
||||
public static void assertTrue(boolean val) {
|
||||
public static void assertTrue( boolean val )
|
||||
{
|
||||
if (! val) {
|
||||
Log.e( TAG, "firing assert!" );
|
||||
DbgUtils.printStack( TAG );
|
||||
|
|
|
@ -919,17 +919,21 @@ public class BoardCanvas extends Canvas implements DrawCtx {
|
|||
Drawable arrow = res.getDrawable( resID );
|
||||
|
||||
if ( !useDark ) {
|
||||
Bitmap src = ((BitmapDrawable)arrow).getBitmap();
|
||||
Bitmap bitmap = src.copy( Bitmap.Config.ARGB_8888, true );
|
||||
Bitmap bitmap = Bitmap.createBitmap( arrow.getIntrinsicWidth(),
|
||||
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 yy = 0; yy < bitmap.getHeight(); ++yy ) {
|
||||
for ( int yy = 0; yy < bitmap.getHeight(); ++yy ) {
|
||||
if ( BLACK == bitmap.getPixel( xx, yy ) ) {
|
||||
bitmap.setPixel( xx, yy, WHITE );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arrow = new BitmapDrawable(bitmap);
|
||||
arrow = new BitmapDrawable( bitmap );
|
||||
}
|
||||
return arrow;
|
||||
}
|
||||
|
|
|
@ -614,7 +614,10 @@ public class BoardDelegate extends DelegateBase
|
|||
Log.i( TAG, "opening rowid %d", m_rowid );
|
||||
m_haveInvited = args.getBoolean( GameUtils.INVITED, false );
|
||||
m_overNotShown = true;
|
||||
} // init
|
||||
|
||||
private void getLock()
|
||||
{
|
||||
GameLock.getLockThen( m_rowid, 100L, new Handler(), // this doesn't unlock
|
||||
new GameLock.GotLockProc() {
|
||||
@Override
|
||||
|
@ -648,7 +651,7 @@ public class BoardDelegate extends DelegateBase
|
|||
}
|
||||
}
|
||||
} );
|
||||
} // init
|
||||
} // getLock
|
||||
|
||||
@Override
|
||||
protected void onStart()
|
||||
|
@ -670,6 +673,7 @@ public class BoardDelegate extends DelegateBase
|
|||
doResume( false );
|
||||
} else {
|
||||
m_resumeSkipped = true;
|
||||
getLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -688,9 +692,11 @@ public class BoardDelegate extends DelegateBase
|
|||
@Override
|
||||
protected void onStop()
|
||||
{
|
||||
if ( isFinishing() && null != m_jniThreadRef ) {
|
||||
if ( null != m_jniThreadRef ) {
|
||||
m_jniThreadRef.release();
|
||||
m_jniThreadRef = null;
|
||||
} else {
|
||||
Log.d( TAG, "onStop(): m_jniThreadRef already null" );
|
||||
}
|
||||
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
|
||||
public void playerScoreHeld( int player )
|
||||
{
|
||||
|
@ -1868,7 +1888,7 @@ public class BoardDelegate extends DelegateBase
|
|||
post( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showToast( text );
|
||||
makeOkOnlyBuilder( text ).show();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||
{ DICTNAME, "TEXT" }
|
||||
,{ LOC, "UNSIGNED INTEGER(1)" }
|
||||
,{ WORDCOUNTS, "TEXT" }
|
||||
,{ ITERMIN, "INTEGRE(4)" }
|
||||
,{ ITERMIN, "INTEGER(4)" }
|
||||
,{ ITERMAX, "INTEGER(4)" }
|
||||
,{ ITERPOS, "INTEGER" }
|
||||
,{ ITERTOP, "INTEGER" }
|
||||
|
|
|
@ -22,13 +22,15 @@ package org.eehouse.android.xw4;
|
|||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
|
@ -1104,26 +1106,28 @@ public class DBUtils {
|
|||
|
||||
public static byte[] loadGame( Context context, GameLock lock )
|
||||
{
|
||||
byte[] result = null;
|
||||
long rowid = lock.getRowid();
|
||||
Assert.assertTrue( ROWID_NOTFOUND != rowid );
|
||||
byte[] result = getCached( rowid );
|
||||
if ( null == result ) {
|
||||
String[] columns = { DBHelper.SNAPSHOT };
|
||||
String selection = String.format( ROW_ID_FMT, rowid );
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
|
||||
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
|
||||
result = cursor.getBlob( cursor
|
||||
.getColumnIndex(DBHelper.SNAPSHOT));
|
||||
} else {
|
||||
Log.e( TAG, "loadGame: none for rowid=%d", rowid );
|
||||
if ( Quarantine.safeToOpen( rowid ) ) {
|
||||
result = getCached( rowid );
|
||||
if ( null == result ) {
|
||||
String[] columns = { DBHelper.SNAPSHOT };
|
||||
String selection = String.format( ROW_ID_FMT, rowid );
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
Cursor cursor = query( TABLE_NAMES.SUM, columns, selection );
|
||||
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
|
||||
result = cursor.getBlob( cursor
|
||||
.getColumnIndex(DBHelper.SNAPSHOT));
|
||||
} else {
|
||||
Log.e( TAG, "loadGame: none for rowid=%d", rowid );
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
cursor.close();
|
||||
setCached( rowid, result );
|
||||
}
|
||||
setCached( rowid, result );
|
||||
}
|
||||
Assert.assertTrueNR( null != result );
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1539,7 +1543,7 @@ public class DBUtils {
|
|||
}
|
||||
s_groupsCache = result;
|
||||
}
|
||||
Log.d( TAG, "getGroups() => %s", result );
|
||||
// Log.d( TAG, "getGroups() => %s", result );
|
||||
return result;
|
||||
} // 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()
|
||||
{
|
||||
return String.format( "%s_%s", DBHelper.getDBName(),
|
||||
|
|
|
@ -51,6 +51,10 @@ public class DlgDelegate {
|
|||
DWNLD_LOC_DICT,
|
||||
NEW_GAME_DFLT_NAME,
|
||||
SEND_EMAIL,
|
||||
WRITE_LOG_DB,
|
||||
CLEAR_LOG_DB,
|
||||
QUARANTINE_CLEAR,
|
||||
QUARANTINE_DELETE,
|
||||
|
||||
// BoardDelegate
|
||||
UNDO_LAST_ACTION,
|
||||
|
|
|
@ -73,7 +73,7 @@ public class DupeModeTimer extends BroadcastReceiver {
|
|||
public void gameSaved( Context context, long rowid,
|
||||
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 ) {
|
||||
case GAME_CHANGED:
|
||||
case GAME_CREATED:
|
||||
|
@ -81,7 +81,7 @@ public class DupeModeTimer extends BroadcastReceiver {
|
|||
if ( sDirtyVals.containsKey( rowid ) ) {
|
||||
sQueue.addOne( context, rowid );
|
||||
} else {
|
||||
Log.d( TAG, "skipping; not dirty" );
|
||||
// Log.d( TAG, "skipping; not dirty" );
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -386,6 +386,16 @@ public class GameUtils {
|
|||
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,
|
||||
UtilCtxt util, TransportProcs tp,
|
||||
byte[] stream, long rowid )
|
||||
|
@ -832,22 +842,28 @@ public class GameUtils {
|
|||
|
||||
public static String[] dictNames( Context context, GameLock lock )
|
||||
{
|
||||
String[] result = null;
|
||||
byte[] stream = savedGame( context, lock );
|
||||
CurGameInfo gi = new CurGameInfo( context );
|
||||
XwJNI.gi_from_stream( gi, stream );
|
||||
return gi.dictNames();
|
||||
CurGameInfo gi = giFromStream( context, stream );
|
||||
if ( null != gi ) {
|
||||
result = gi.dictNames();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String[] dictNames( Context context, long rowid,
|
||||
int[] missingLang )
|
||||
{
|
||||
String[] result = null;
|
||||
byte[] stream = savedGame( context, rowid );
|
||||
CurGameInfo gi = new CurGameInfo( context );
|
||||
XwJNI.gi_from_stream( gi, stream );
|
||||
if ( null != missingLang ) {
|
||||
missingLang[0] = gi.dictLang;
|
||||
CurGameInfo gi = giFromStream( context, stream );
|
||||
if ( null != gi ) {
|
||||
if ( null != missingLang ) {
|
||||
missingLang[0] = gi.dictLang;
|
||||
}
|
||||
result = gi.dictNames();
|
||||
}
|
||||
return gi.dictNames();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String[] dictNames( Context context, long rowid )
|
||||
|
@ -863,7 +879,7 @@ public class GameUtils {
|
|||
public static boolean gameDictsHere( Context context, GameLock 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
|
||||
|
@ -873,7 +889,8 @@ public class GameUtils {
|
|||
int[] 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,
|
||||
|
@ -1089,34 +1106,37 @@ public class GameUtils {
|
|||
boolean success;
|
||||
try ( GameLock lock = GameLock.lock( rowid, 300 ) ) {
|
||||
success = null != lock;
|
||||
if ( success ) {
|
||||
byte[] stream = savedGame( context, lock );
|
||||
CurGameInfo gi = new CurGameInfo( context );
|
||||
XwJNI.gi_from_stream( gi, stream );
|
||||
|
||||
// first time required so dictNames() will work
|
||||
gi.replaceDicts( context, newDict );
|
||||
|
||||
String[] dictNames = gi.dictNames();
|
||||
DictUtils.DictPairs pairs = DictUtils.openDicts( context,
|
||||
dictNames );
|
||||
|
||||
try ( GamePtr gamePtr =
|
||||
XwJNI.initFromStream( rowid, stream, gi, dictNames,
|
||||
pairs.m_bytes, pairs.m_paths,
|
||||
gi.langName( context ), null,
|
||||
null, CommonPrefs.get( context ), null ) ) {
|
||||
// second time required as game_makeFromStream can overwrite
|
||||
gi.replaceDicts( context, newDict );
|
||||
|
||||
saveGame( context, gamePtr, gi, lock, false );
|
||||
|
||||
summarize( context, lock, gamePtr, gi );
|
||||
}
|
||||
} else {
|
||||
if ( !success ) {
|
||||
DbgUtils.toastNoLock( TAG, context, rowid,
|
||||
"replaceDicts(): rowid %d",
|
||||
rowid );
|
||||
} else {
|
||||
byte[] stream = savedGame( context, lock );
|
||||
CurGameInfo gi = giFromStream( context, stream );
|
||||
success = null != gi;
|
||||
if ( !success ) {
|
||||
Log.e( TAG, "replaceDicts(): unable to load rowid %d", rowid );
|
||||
} else {
|
||||
// first time required so dictNames() will work
|
||||
gi.replaceDicts( context, newDict );
|
||||
|
||||
String[] dictNames = gi.dictNames();
|
||||
DictUtils.DictPairs pairs = DictUtils.openDicts( context,
|
||||
dictNames );
|
||||
|
||||
try ( GamePtr gamePtr =
|
||||
XwJNI.initFromStream( rowid, stream, gi, dictNames,
|
||||
pairs.m_bytes, pairs.m_paths,
|
||||
gi.langName( context ), null,
|
||||
null, CommonPrefs.get( context ), null ) ) {
|
||||
// second time required as game_makeFromStream can overwrite
|
||||
gi.replaceDicts( context, newDict );
|
||||
|
||||
saveGame( context, gamePtr, gi, lock, false );
|
||||
|
||||
summarize( context, lock, gamePtr, gi );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
|
|
|
@ -62,6 +62,7 @@ import org.eehouse.android.xw4.jni.GameSummary;
|
|||
import org.eehouse.android.xw4.jni.LastMoveInfo;
|
||||
import org.eehouse.android.xw4.loc.LocUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
@ -574,6 +575,7 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
// R.id.games_menu_loaddb,
|
||||
R.id.games_menu_storedb,
|
||||
R.id.games_menu_writegit,
|
||||
R.id.games_submenu_logs,
|
||||
};
|
||||
private static final int[] NOSEL_ITEMS = {
|
||||
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
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
@ -1235,13 +1258,7 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
// an empty room name.
|
||||
if ( clicked instanceof GameListItem ) {
|
||||
long rowid = ((GameListItem)clicked).getRowID();
|
||||
if ( ! m_launchedGames.contains( rowid ) ) {
|
||||
makeNotAgainBuilder( R.string.not_again_newselect,
|
||||
R.string.key_notagain_newselect,
|
||||
Action.OPEN_GAME )
|
||||
.setParams( rowid, summary )
|
||||
.show();
|
||||
}
|
||||
openWithChecks( rowid, summary );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1342,6 +1359,18 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
case OPEN_GAME:
|
||||
doOpenGame( params );
|
||||
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:
|
||||
clearSelections();
|
||||
break;
|
||||
|
@ -1380,6 +1409,30 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
Utils.emailAuthor( m_activity );
|
||||
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:
|
||||
rematchWithNameAndPerm( true, params );
|
||||
break;
|
||||
|
@ -1664,6 +1717,23 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
Utils.gitInfoToClip( m_activity );
|
||||
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:
|
||||
handled = handleSelGamesItem( itemID, selRowIDs )
|
||||
|| handleSelGroupsItem( itemID, getSelGroupIDs() );
|
||||
|
@ -1723,6 +1793,7 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
&& (BuildConfig.DEBUG || XWPrefs.getDebugEnabled( m_activity ));
|
||||
}
|
||||
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 );
|
||||
|
||||
enable = !m_launchedGames.contains( rowID );
|
||||
|
@ -1875,6 +1946,7 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
} else {
|
||||
dropSels = true; // will select the new game instead
|
||||
post( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Activity self = m_activity;
|
||||
byte[] stream =
|
||||
|
@ -1918,6 +1990,11 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
makeOkOnlyBuilder( msg ).show();
|
||||
break;
|
||||
|
||||
// DEBUG only
|
||||
case R.id.games_game_markbad:
|
||||
Quarantine.recordOpened( selRowIDs[0] );
|
||||
break;
|
||||
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class InviteChoicesAlert extends DlgDelegateAlert {
|
|||
if ( Utils.deviceSupportsNBS(context) ) {
|
||||
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 );
|
||||
}
|
||||
if ( WiDirWrapper.enabled() ) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* -*- 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.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -20,22 +20,91 @@
|
|||
|
||||
package org.eehouse.android.xw4;
|
||||
|
||||
import android.content.ContentValues;
|
||||
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;
|
||||
|
||||
public class Log {
|
||||
private static final String TAG = Log.class.getSimpleName();
|
||||
private static final String PRE_TAG = BuildConfig.FLAVOR + "-";
|
||||
private static final String KEY_USE_DB = TAG + "/useDB";
|
||||
private static final boolean LOGGING_ENABLED
|
||||
= BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD;
|
||||
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 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 )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
boolean on = LOGGING_ENABLED ||
|
||||
|
@ -47,35 +116,54 @@ public class Log {
|
|||
public static void d( String tag, String fmt, Object... args )
|
||||
{
|
||||
if ( sEnabled ) {
|
||||
String str = new Formatter().format( fmt, args ).toString();
|
||||
android.util.Log.d( PRE_TAG + tag, str );
|
||||
dolog( LOG_LEVEL.DEBUG, tag, fmt, args );
|
||||
}
|
||||
}
|
||||
|
||||
public static void w( String tag, String fmt, Object... args )
|
||||
{
|
||||
if ( sEnabled ) {
|
||||
String str = new Formatter().format( fmt, args ).toString();
|
||||
android.util.Log.w( PRE_TAG + tag, str );
|
||||
dolog( LOG_LEVEL.WARN, tag, fmt, args );
|
||||
}
|
||||
}
|
||||
|
||||
public static void e( String tag, String fmt, Object... args )
|
||||
{
|
||||
if ( ERROR_LOGGING_ENABLED ) {
|
||||
String str = new Formatter().format( fmt, args ).toString();
|
||||
android.util.Log.e( PRE_TAG + tag, str );
|
||||
dolog( LOG_LEVEL.ERROR, tag, fmt, args );
|
||||
}
|
||||
}
|
||||
|
||||
public static void i( String tag, String fmt, Object... args )
|
||||
{
|
||||
if ( sEnabled ) {
|
||||
String str = new Formatter().format( fmt, args ).toString();
|
||||
android.util.Log.i( PRE_TAG + tag, str );
|
||||
dolog( LOG_LEVEL.INFO, tag, fmt, args );
|
||||
}
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
if ( sEnabled ) {
|
||||
|
@ -83,4 +171,138 @@ public class Log {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -992,7 +992,7 @@ public class NFCUtils {
|
|||
mAdapter.disableReaderMode( mActivity );
|
||||
}
|
||||
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
|
||||
// mode soon, sleep until interrupted by a state change,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -57,6 +57,8 @@ import org.eehouse.android.xw4.DlgDelegate.Action;
|
|||
public class RelayInviteDelegate extends InviteDelegate {
|
||||
private static final String TAG = RelayInviteDelegate.class.getSimpleName();
|
||||
private static final String RECS_KEY = "TAG" + "/recs";
|
||||
private static final boolean RELAYINVITE_SUPPORTED
|
||||
= BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD;
|
||||
|
||||
private static int[] BUTTONIDS = {
|
||||
R.id.button_relay_add,
|
||||
|
@ -77,7 +79,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
SentInvitesInfo info,
|
||||
RequestCode requestCode )
|
||||
{
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
Intent intent =
|
||||
InviteDelegate.makeIntent( activity, RelayInviteActivity.class,
|
||||
nMissing, info );
|
||||
|
@ -94,7 +96,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
@Override
|
||||
protected void init( Bundle savedInstanceState )
|
||||
{
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
super.init( savedInstanceState );
|
||||
|
||||
String msg = getString( R.string.button_invite );
|
||||
|
@ -137,7 +139,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
@Override
|
||||
protected void onBarButtonClicked( int id )
|
||||
{
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
switch( id ) {
|
||||
case R.id.button_relay_add:
|
||||
Utils.notImpl( m_activity );
|
||||
|
@ -189,7 +191,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
protected Dialog makeDialog( DBAlert alert, Object[] params )
|
||||
{
|
||||
Dialog dialog = null;
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
DialogInterface.OnClickListener lstnr;
|
||||
switch( alert.getDlgID() ) {
|
||||
case GET_NUMBER: {
|
||||
|
@ -334,7 +336,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
@Override
|
||||
protected void tryEnable()
|
||||
{
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
super.tryEnable();
|
||||
|
||||
Button button = (Button)findViewById( R.id.button_clear );
|
||||
|
@ -353,7 +355,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
public boolean onPosButton( Action action, Object[] params )
|
||||
{
|
||||
boolean handled = true;
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
switch( action ) {
|
||||
case CLEAR_ACTION:
|
||||
clearSelectedImpl();
|
||||
|
@ -373,7 +375,7 @@ public class RelayInviteDelegate extends InviteDelegate {
|
|||
public boolean onDismissed( Action action, Object[] params )
|
||||
{
|
||||
boolean handled = true;
|
||||
if ( BuildConfig.RELAYINVITE_SUPPORTED ) {
|
||||
if ( RELAYINVITE_SUPPORTED ) {
|
||||
if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) {
|
||||
makeConfirmThenBuilder( R.string.warn_unlimited,
|
||||
Action.POST_WARNING_ACTION )
|
||||
|
|
|
@ -65,6 +65,7 @@ import java.io.ObjectInputStream;
|
|||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Formatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
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 );
|
||||
msg = new Formatter().format( msg, args ).toString();
|
||||
showToast( context, msg );
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,8 @@ public class XWApp extends Application
|
|||
Assert.assertTrue( s_context == s_context.getApplicationContext() );
|
||||
super.onCreate();
|
||||
|
||||
Log.init( this );
|
||||
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||
|
||||
android.util.Log.i( TAG, "onCreate(); git_rev="
|
||||
|
|
|
@ -80,6 +80,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
|
||||
protected void onCreate( DelegateBase dlgt, Bundle sis, boolean hasOptionsMenu )
|
||||
{
|
||||
Log.d( TAG, "%H/%s.onCreate() called", this, getClass().getSimpleName() );
|
||||
m_hasOptionsMenu = hasOptionsMenu;
|
||||
this.onCreate( dlgt, sis );
|
||||
}
|
||||
|
@ -87,7 +88,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
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 );
|
||||
outState.putString( PARENT_NAME, m_parentName );
|
||||
outState.putInt( COMMIT_ID, m_commitID );
|
||||
|
@ -97,7 +98,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
|
||||
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 );
|
||||
if ( null != sis ) {
|
||||
m_parentName = sis.getString( PARENT_NAME );
|
||||
|
@ -122,7 +123,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
public View onCreateView( LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState )
|
||||
{
|
||||
Log.d( TAG, "%s.onCreateView() called", getClass().getSimpleName() );
|
||||
Log.d( TAG, "%H/%s.onCreateView() called", this, getClass().getSimpleName() );
|
||||
sActiveFrags.add(this);
|
||||
return m_dlgt.inflateView( inflater, container );
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
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 );
|
||||
super.onActivityCreated( savedInstanceState );
|
||||
if ( m_hasOptionsMenu ) {
|
||||
|
@ -141,7 +142,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
Log.d( TAG, "%s.onPause() called", getClass().getSimpleName() );
|
||||
Log.d( TAG, "%H/%s.onPause() called", this, getClass().getSimpleName() );
|
||||
m_dlgt.onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
Log.d( TAG, "%s.onResume() called", getClass().getSimpleName() );
|
||||
Log.d( TAG, "%H/%s.onResume() called", this, getClass().getSimpleName() );
|
||||
super.onResume();
|
||||
m_dlgt.onResume();
|
||||
}
|
||||
|
@ -157,7 +158,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
Log.d( TAG, "%s.onStart() called", getClass().getSimpleName() );
|
||||
Log.d( TAG, "%H/%s.onStart() called", this, getClass().getSimpleName() );
|
||||
super.onStart();
|
||||
m_dlgt.onStart();
|
||||
}
|
||||
|
@ -165,7 +166,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
Log.d( TAG, "%s.onStop() called", getClass().getSimpleName() );
|
||||
Log.d( TAG, "%H/%s.onStop() called", this, getClass().getSimpleName() );
|
||||
m_dlgt.onStop();
|
||||
super.onStop();
|
||||
}
|
||||
|
@ -173,7 +174,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
Log.d( TAG, "%s.onDestroy() called", getClass().getSimpleName() );
|
||||
Log.d( TAG, "%H/%s.onDestroy() called", this, getClass().getSimpleName() );
|
||||
m_dlgt.onDestroy();
|
||||
sActiveFrags.remove( this );
|
||||
super.onDestroy();
|
||||
|
@ -182,7 +183,7 @@ abstract class XWFragment extends Fragment implements Delegator {
|
|||
@Override
|
||||
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],
|
||||
resultCode, data );
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ public class CurGameInfo implements Serializable {
|
|||
private static final String PHONIES = "PHONIES";
|
||||
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 String dictName;
|
||||
|
|
|
@ -123,9 +123,8 @@ public class DUtilCtxt {
|
|||
private static final int STRSD_DUP_ONESCORE = 29;
|
||||
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;
|
||||
switch( stringCode ) {
|
||||
case STR_ROBOT_MOVED:
|
||||
|
@ -217,7 +216,7 @@ public class DUtilCtxt {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -197,9 +197,13 @@ public class JNIThread extends Thread implements AutoCloseable {
|
|||
m_queue.clear();
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
DictUtils.DictPairs pairs = null;
|
||||
String[] dictNames = GameUtils.dictNames( context, m_lock );
|
||||
DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames );
|
||||
boolean success = !pairs.anyMissing( dictNames );
|
||||
if ( null != dictNames ) {
|
||||
pairs = DictUtils.openDicts( context, dictNames );
|
||||
success = !pairs.anyMissing( dictNames );
|
||||
}
|
||||
|
||||
if ( success ) {
|
||||
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
|
||||
// instead save the hash. Also, update it after each save.
|
||||
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 {
|
||||
// Don't need this!!!! this only runs on the run() thread
|
||||
synchronized( this ) {
|
||||
|
|
|
@ -62,6 +62,7 @@ public interface UtilCtxt {
|
|||
void remSelected();
|
||||
void timerSelected( boolean inDuplicateMode, boolean canPause );
|
||||
void setIsServer( boolean isServer );
|
||||
void informWordBlocked( String word, String dict );
|
||||
|
||||
void bonusSquareHeld( int bonus );
|
||||
void playerScoreHeld( int player );
|
||||
|
|
|
@ -106,6 +106,12 @@ public class UtilCtxtImpl implements UtilCtxt {
|
|||
subclassOverride( "setIsServer" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void informWordBlocked( String word, String dict )
|
||||
{
|
||||
subclassOverride( "informWordBlocked" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bonusSquareHeld( int bonus )
|
||||
{
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.eehouse.android.xw4.Assert;
|
|||
import org.eehouse.android.xw4.BuildConfig;
|
||||
import org.eehouse.android.xw4.Log;
|
||||
import org.eehouse.android.xw4.NetLaunchInfo;
|
||||
import org.eehouse.android.xw4.Quarantine;
|
||||
import org.eehouse.android.xw4.Utils;
|
||||
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
|
||||
|
||||
|
@ -46,6 +47,7 @@ public class XwJNI {
|
|||
m_ptrGame = ptr;
|
||||
m_rowid = rowid;
|
||||
mStack = android.util.Log.getStackTraceString(new Exception());
|
||||
Quarantine.recordOpened( rowid );
|
||||
}
|
||||
|
||||
public synchronized long ptr()
|
||||
|
@ -73,6 +75,7 @@ public class XwJNI {
|
|||
// getClass().getName(), this, m_rowid, m_refCount );
|
||||
if ( 0 == m_refCount ) {
|
||||
if ( 0 != m_ptrGame ) {
|
||||
Quarantine.recordClosed( m_rowid );
|
||||
if ( haveEnv( getJNI().m_ptrGlobals ) ) {
|
||||
game_dispose( this ); // will crash if haveEnv fails
|
||||
} else {
|
||||
|
@ -165,6 +168,7 @@ public class XwJNI {
|
|||
|
||||
public static void gi_from_stream( CurGameInfo gi, byte[] stream )
|
||||
{
|
||||
Assert.assertNotNull( stream );
|
||||
gi_from_stream( getJNI().m_ptrGlobals, gi, stream ); // called here
|
||||
}
|
||||
|
||||
|
@ -308,8 +312,10 @@ public class XwJNI {
|
|||
// int timerHeight );
|
||||
public static native boolean board_zoom( GamePtr gamePtr, int zoomBy,
|
||||
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,
|
||||
int xx, int yy,
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
|
||||
<LinearLayout android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
>
|
||||
|
||||
<CheckBox android:id="@+id/remote_check"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -36,43 +38,49 @@
|
|||
android:layout_width="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:text="@string/player_label"
|
||||
android:gravity="left"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
||||
<EditText android:id="@+id/player_name_edit"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:scrollHorizontally="true"
|
||||
android:selectAllOnFocus="true"
|
||||
android:gravity="fill_horizontal"
|
||||
android:maxLines="1"
|
||||
android:maxLength="32"
|
||||
android:inputType="textCapWords"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
>
|
||||
<TextView android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/player_label"
|
||||
android:gravity="left"
|
||||
android:layout_marginRight="5dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
||||
<TextView android:id="@+id/dict_label"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:gravity="left"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
||||
<Spinner android:id="@+id/dict_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawSelectorOnTop="true"
|
||||
/>
|
||||
<EditText android:id="@+id/player_name_edit"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:scrollHorizontally="true"
|
||||
android:selectAllOnFocus="true"
|
||||
android:gravity="fill_horizontal"
|
||||
android:maxLines="1"
|
||||
android:maxLength="32"
|
||||
android:inputType="textCapWords"
|
||||
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"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:gravity="left"
|
||||
android:layout_marginRight="5dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
||||
<Spinner android:id="@+id/dict_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawSelectorOnTop="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox android:id="@+id/robot_check"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -83,13 +91,11 @@
|
|||
<LinearLayout android:id="@+id/password_set"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<TextView android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:text="@string/password_label"
|
||||
android:gravity="left"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
|
@ -98,8 +104,6 @@
|
|||
<EditText android:id="@+id/password_edit"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:scrollHorizontally="true"
|
||||
android:gravity="fill_horizontal"
|
||||
android:maxLines="1"
|
||||
|
|
|
@ -38,5 +38,7 @@
|
|||
<item android:id="@+id/games_game_invites"
|
||||
android:title="@string/board_menu_game_showInvites"
|
||||
/>
|
||||
|
||||
<item android:id="@+id/games_game_markbad"
|
||||
android:title="@string/list_item_markbad"
|
||||
/>
|
||||
</menu>
|
||||
|
|
|
@ -122,4 +122,22 @@
|
|||
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>
|
||||
|
|
|
@ -242,6 +242,7 @@
|
|||
<item>Czech</item>
|
||||
<item>Greek</item>
|
||||
<item>Slovak</item>
|
||||
<item>Hungarian</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="language_codes">
|
||||
|
@ -265,6 +266,7 @@
|
|||
<item>cs</item>
|
||||
<item>el</item>
|
||||
<item>sk</item>
|
||||
<item>hu</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Triples of Name, supported codes, and URL format string -->
|
||||
|
|
|
@ -417,7 +417,7 @@
|
|||
player will use. The language the game will use (which
|
||||
constrains the choice of wordlists) is substituted in for
|
||||
"%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
|
||||
above the list of selectable items. The language the game
|
||||
will use is substituted in for "%1$s". -->
|
||||
|
@ -425,13 +425,13 @@
|
|||
<!-- checkbox determining if player is robot/automated or human -->
|
||||
<string name="robot_label">Robot player</string>
|
||||
<!-- 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
|
||||
sense for a robot to have a password. In fact, passwords
|
||||
only make sense where there's more than one local human
|
||||
player on a device, so they are infrequently used in network
|
||||
games as well. -->
|
||||
<string name="password_label">Password</string>
|
||||
<string name="password_label">Password:</string>
|
||||
<!--
|
||||
############################################################
|
||||
# :Screens:
|
||||
|
@ -2306,6 +2306,8 @@
|
|||
<string name="processing_games">Processing games</string>
|
||||
<string name="list_item_select">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
|
||||
player name \"%1$s\". Would you like to personalize with your own
|
||||
name before you create this game?</string>
|
||||
|
@ -2519,4 +2521,30 @@
|
|||
<string name="history_pause_fmt">Paused by: %1$s.</string>
|
||||
<string name="history_msg_fmt"> Message: %1$s.</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>
|
||||
|
|
|
@ -62,18 +62,18 @@
|
|||
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"
|
||||
android:title="@string/nethints_allowed"
|
||||
android:summary="@string/nethints_allowed_sum"
|
||||
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"
|
||||
android:title="@string/init_autojuggle"
|
||||
android:summary="@string/init_autojuggle_sum"
|
||||
|
@ -401,7 +401,7 @@
|
|||
<CheckBoxPreference android:key="@string/key_enable_dup_invite"
|
||||
android:title="@string/enable_dupes_title"
|
||||
android:summary="@string/enable_dupes_summary"
|
||||
android:defaultValue="false"
|
||||
android:defaultValue="@bool/DEBUG"
|
||||
/>
|
||||
|
||||
<CheckBoxPreference android:key="@string/key_enable_pending_count"
|
||||
|
|
17
xwords4/android/app/src/xw4d/res/values/tmp_for_phony.xml
Normal file
17
xwords4/android/app/src/xw4d/res/values/tmp_for_phony.xml
Normal 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>
|
1
xwords4/android/app/src/xw4dNoSMS/res/values/tmp_for_phony.xml
Symbolic link
1
xwords4/android/app/src/xw4dNoSMS/res/values/tmp_for_phony.xml
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../xw4d/res/values/tmp_for_phony.xml
|
|
@ -30,6 +30,8 @@
|
|||
void
|
||||
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",
|
||||
test, line, func, file );
|
||||
__android_log_assert( test, "ASSERT", "line %d in %s() in %s",
|
||||
|
@ -424,10 +426,13 @@ setIntInArray( JNIEnv* env, jintArray arr, int index, int val )
|
|||
jobjectArray
|
||||
makeStringArray( JNIEnv *env, const int count, const XP_UCHAR** vals )
|
||||
{
|
||||
jclass clas = (*env)->FindClass(env, "java/lang/String");
|
||||
jstring empty = (*env)->NewStringUTF( env, "" );
|
||||
jobjectArray jarray = (*env)->NewObjectArray( env, count, clas, empty );
|
||||
deleteLocalRefs( env, clas, empty, DELETE_NO_REF );
|
||||
jobjectArray jarray;
|
||||
{
|
||||
jclass clas = (*env)->FindClass(env, "java/lang/String");
|
||||
jstring empty = (*env)->NewStringUTF( env, "" );
|
||||
jarray = (*env)->NewObjectArray( env, count, clas, empty );
|
||||
deleteLocalRefs( env, clas, empty, DELETE_NO_REF );
|
||||
}
|
||||
|
||||
for ( int ii = 0; !!vals && ii < count; ++ii ) {
|
||||
jstring jstr = (*env)->NewStringUTF( env, vals[ii] );
|
||||
|
@ -763,6 +768,28 @@ deleteLocalRefs( JNIEnv* env, ... )
|
|||
}
|
||||
|
||||
#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
|
||||
debugf( const char* format, va_list ap )
|
||||
{
|
||||
|
@ -780,8 +807,8 @@ debugf( const char* format, va_list ap )
|
|||
if ( len < sizeof(buf) ) {
|
||||
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
|
||||
"xw4"
|
||||
# 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
|
||||
"x4du"
|
||||
# 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
|
||||
|
|
|
@ -108,5 +108,16 @@ XP_U32 getCurSeconds( JNIEnv* env );
|
|||
|
||||
void deleteLocalRef( JNIEnv* env, jobject jobj );
|
||||
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 */
|
||||
#endif
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "andutils.h"
|
||||
#include "paths.h"
|
||||
#include "LocalizedStrIncludes.h"
|
||||
#include "dbgutil.h"
|
||||
|
||||
#define MAX_QUANTITY_STRS 4
|
||||
|
||||
|
@ -705,6 +706,17 @@ and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer )
|
|||
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
|
||||
static const XP_UCHAR*
|
||||
and_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ )
|
||||
|
@ -907,8 +919,10 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
|
|||
#endif
|
||||
|
||||
SET_PROC(getDevUtilCtxt);
|
||||
SET_PROC(informWordBlocked);
|
||||
|
||||
#undef SET_PROC
|
||||
assertTableFull( vtable, sizeof(*vtable), "util" );
|
||||
return (XW_UtilCtxt*)util;
|
||||
} /* makeUtil */
|
||||
|
||||
|
@ -965,6 +979,9 @@ makeDUtil( MPFORMAL EnvThreadInfo* ti, jobject jdutil, VTableMgr* vtMgr,
|
|||
SET_DPROC(notifyPause);
|
||||
SET_DPROC(onDupTimerChanged);
|
||||
|
||||
#undef SET_DPROC
|
||||
|
||||
assertTableFull( vtable, sizeof(*vtable), "dutil" );
|
||||
return &dutil->dutil;
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ typedef long GamePtrType;
|
|||
#ifdef LOG_MAPPING
|
||||
# ifdef DEBUG
|
||||
static int
|
||||
countUsed(const EnvThreadInfo* ti)
|
||||
countUsed( const EnvThreadInfo* ti )
|
||||
{
|
||||
int count = 0;
|
||||
for ( int ii = 0; ii < ti->nEntries; ++ii ) {
|
||||
|
@ -157,8 +157,8 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller )
|
|||
found = true;
|
||||
if ( env != entry->env ) {
|
||||
/* this DOES happen!!! */
|
||||
XP_LOGF( "%s (ti=%p): replacing env %p with env %p for thread %x",
|
||||
__func__, ti, entry->env, env, (int)self );
|
||||
RAW_LOG( "(ti=%p): replacing env %p with env %p for thread %x",
|
||||
ti, entry->env, env, (int)self );
|
||||
entry->env = env;
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller )
|
|||
ti->entries = entries;
|
||||
ti->nEntries = nEntries;
|
||||
#ifdef LOG_MAPPING
|
||||
XP_LOGF( "%s: num env entries now %d", __func__, nEntries );
|
||||
RAW_LOG( "num env entries now %d", nEntries );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -191,9 +191,9 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller )
|
|||
++firstEmpty->refcount;
|
||||
#ifdef LOG_MAPPING
|
||||
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 );
|
||||
XP_LOGF( "%s: num entries USED now %d", __func__, countUsed(ti) );
|
||||
RAW_LOG( "num entries USED now %d", countUsed(ti) );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -221,9 +221,9 @@ map_remove_prv( EnvThreadInfo* ti, JNIEnv* env, const char* func )
|
|||
if ( found ) {
|
||||
XP_ASSERT( pthread_self() == entry->owner );
|
||||
#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 );
|
||||
XP_LOGF( "%s: %d entries left", __func__, countUsed( ti ) );
|
||||
RAW_LOG( "%d entries left", countUsed( ti ) );
|
||||
entry->ownerFunc = NULL;
|
||||
#endif
|
||||
XP_ASSERT( 1 == entry->refcount );
|
||||
|
@ -258,6 +258,46 @@ prvEnvForMe( EnvThreadInfo* ti )
|
|||
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*
|
||||
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->smsProto = smsproto_init( MPPARM( mpool ) globalState->dutil );
|
||||
MPASSIGN( globalState->mpool, mpool );
|
||||
setGlobalState( globalState );
|
||||
// LOG_RETURNF( "%p", globalState );
|
||||
return (jlong)globalState;
|
||||
}
|
||||
|
@ -335,6 +376,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals
|
|||
{
|
||||
// LOG_FUNC();
|
||||
if ( 0 != jniGlobalPtr ) {
|
||||
setGlobalState( NULL );
|
||||
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
|
||||
#ifdef MEM_DEBUG
|
||||
MemPoolCtx* mpool = GETMPOOL( globalState );
|
||||
|
@ -984,7 +1026,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame
|
|||
}
|
||||
globals->dctx = dctx;
|
||||
globals->xportProcs = makeXportProcs( MPPARM(mpool) ti, j_procs );
|
||||
CommonPrefs cp;
|
||||
CommonPrefs cp = {0};
|
||||
loadCommonPrefs( env, &cp, j_cp );
|
||||
|
||||
game_makeNewGame( MPPARM(mpool) &state->game, gi, globals->util, dctx, &cp,
|
||||
|
|
|
@ -204,7 +204,7 @@ def getOrderedApks( path, appID, debug ):
|
|||
files = ((os.stat(apk).st_mtime, apk) for apk in glob.glob(pattern))
|
||||
for mtime, file in sorted(files, reverse=True):
|
||||
info = getAAPTInfo(file)
|
||||
if info['appID'] == appID:
|
||||
if info and 'appID' in info and info['appID'] == appID:
|
||||
apkToCode[file] = info['versionCode']
|
||||
apkToMtime[file] = mtime
|
||||
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"
|
||||
% (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 )
|
||||
if apk:
|
||||
apk = apk[len(k_filebase):] # strip fs path
|
||||
|
@ -569,7 +569,7 @@ def getUpdates( req, params ):
|
|||
# result[k_XLATEINFO] = xlateResult;
|
||||
|
||||
result = json.dumps( result )
|
||||
# apache.log_error( result )
|
||||
apache.log_error( 'getUpdates() => ' + result )
|
||||
return result
|
||||
|
||||
def clearShelf():
|
||||
|
|
|
@ -1040,19 +1040,13 @@ typedef struct _BadWordList {
|
|||
} BadWordList;
|
||||
|
||||
static void
|
||||
saveBadWords( const XP_UCHAR* word, XP_Bool isLegal,
|
||||
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 )
|
||||
saveBadWords( const WNParams* wnp, void* closure )
|
||||
{
|
||||
if ( !isLegal ) {
|
||||
if ( !wnp->isLegal ) {
|
||||
BadWordList* bwlp = (BadWordList*)closure;
|
||||
bwlp->bwi.words[bwlp->bwi.nWords] = &bwlp->buf[bwlp->index];
|
||||
XP_STRCAT( &bwlp->buf[bwlp->index], word );
|
||||
bwlp->index += XP_STRLEN(word) + 1;
|
||||
XP_STRCAT( &bwlp->buf[bwlp->index], wnp->word );
|
||||
bwlp->index += XP_STRLEN(wnp->word) + 1;
|
||||
++bwlp->bwi.nWords;
|
||||
}
|
||||
} /* saveBadWords */
|
||||
|
@ -1149,7 +1143,7 @@ board_commitTurn( BoardCtxt* board, XP_Bool phoniesConfirmed,
|
|||
bwl.bwi.dictName =
|
||||
dict_getShortName( model_getPlayerDict( model, selPlayer ) );
|
||||
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
|
||||
cancels as otherwise another player could get around
|
||||
passwords and peek at tiles. */
|
||||
|
@ -1341,7 +1335,7 @@ timerFiredForPen( BoardCtxt* board )
|
|||
|
||||
if ( dragDropIsBeingDragged( board, col, row, NULL ) ) {
|
||||
/* 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 */
|
||||
#ifdef XWFEATURE_RAISETILE
|
||||
draw = dragDropSetAdd( board );
|
||||
|
@ -1351,20 +1345,24 @@ timerFiredForPen( BoardCtxt* board )
|
|||
/* We calculate words even for a pending tile set, meaning
|
||||
dragDrop might be happening too. */
|
||||
XP_Bool listWords = XP_FALSE;
|
||||
#ifdef XWFEATURE_BOARDWORDS /* here it is */
|
||||
#ifdef XWFEATURE_BOARDWORDS
|
||||
XP_U16 modelCol, modelRow;
|
||||
flipIf( board, col, row, &modelCol, &modelRow );
|
||||
listWords = model_getTile( board->model, modelCol, modelRow,
|
||||
XP_TRUE, board->selPlayer, NULL,
|
||||
NULL, NULL, NULL );
|
||||
if ( listWords ) {
|
||||
XP_LOGF( "%s(): listWords came back true", __func__ );
|
||||
XWStreamCtxt* stream =
|
||||
mem_stream_make_raw( MPPARM(board->mpool)
|
||||
dutil_getVTManager(board->dutil) );
|
||||
model_listWordsThrough( board->model, modelCol, modelRow,
|
||||
board->selPlayer, stream );
|
||||
util_cellSquareHeld( board->util, stream );
|
||||
listWords = model_listWordsThrough( board->model, modelCol, modelRow,
|
||||
board->selPlayer, stream );
|
||||
if ( listWords ) {
|
||||
util_cellSquareHeld( board->util, stream );
|
||||
if ( dragDropInProgress( board ) ) {
|
||||
dragDropEnd( board, board->penDownX, board->penDownY, NULL );
|
||||
}
|
||||
}
|
||||
stream_destroy( stream );
|
||||
}
|
||||
#endif
|
||||
|
@ -3030,7 +3028,6 @@ handlePenUpInternal( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool isPen,
|
|||
XP_Bool altDown )
|
||||
{
|
||||
XP_Bool draw = XP_FALSE;
|
||||
XP_Bool dragged = XP_FALSE;
|
||||
BoardObjectType prevObj = board->penDownObject;
|
||||
|
||||
/* 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. */
|
||||
board->penDownObject = OBJ_NONE;
|
||||
|
||||
XP_Bool dragged = XP_FALSE;
|
||||
if ( dragDropInProgress(board) ) {
|
||||
draw = dragDropEnd( board, xx, yy, &dragged );
|
||||
}
|
||||
|
|
|
@ -140,8 +140,12 @@ typedef XP_U8 DeviceRole;
|
|||
|
||||
enum {
|
||||
PHONIES_IGNORE,
|
||||
/* You can commit a phony after viewing a warning */
|
||||
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;
|
||||
|
||||
|
@ -233,6 +237,7 @@ typedef struct CommonPrefs {
|
|||
#ifdef XWFEATURE_CROSSHAIRS
|
||||
XP_Bool hideCrosshairs; /* applies to all games */
|
||||
#endif
|
||||
XP_U16 makePhonyPct;
|
||||
} CommonPrefs;
|
||||
|
||||
typedef struct _PlayerDicts {
|
||||
|
|
|
@ -126,5 +126,24 @@ devIDTypeToStr(DevIDType typ)
|
|||
}
|
||||
|
||||
#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 */
|
||||
|
|
|
@ -53,4 +53,10 @@ const char* devIDTypeToStr(DevIDType typ);
|
|||
# define SET_DIRTY( ptr )
|
||||
# endif
|
||||
|
||||
# ifdef DEBUG
|
||||
void assertTableFull( void* table, size_t sizeInBytes, const XP_UCHAR* tableName );
|
||||
# else
|
||||
# define assertTableFull( table, sizeInBytes, tableName )
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -237,7 +237,7 @@ dragDropContinue( BoardCtxt* board, XP_U16 xx, XP_U16 yy )
|
|||
}
|
||||
|
||||
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;
|
||||
BoardObjectType newObj;
|
||||
|
@ -245,7 +245,9 @@ dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged )
|
|||
XP_ASSERT( dragDropInProgress(board) );
|
||||
|
||||
(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
|
||||
don't remove it from its earlier location until it's dropped,
|
||||
|
|
|
@ -1104,16 +1104,10 @@ considerMove( EngineCtxt* engine, Tile* tiles, XP_S16 tileLength,
|
|||
} /* considerMove */
|
||||
|
||||
static void
|
||||
countWords( const XP_UCHAR* XP_UNUSED(word), XP_Bool isLegal,
|
||||
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 )
|
||||
countWords( const WNParams* wnp, void* closure )
|
||||
{
|
||||
XP_U16* wcp = (XP_U16*)closure;
|
||||
if ( isLegal ) {
|
||||
if ( wnp->isLegal ) {
|
||||
++*wcp;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -536,6 +536,7 @@ gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI )
|
|||
void
|
||||
gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere )
|
||||
{
|
||||
LOGGI( gi, "before" );
|
||||
XP_ASSERT( nTotal <= MAX_NUM_PLAYERS );
|
||||
XP_ASSERT( nHere < nTotal );
|
||||
|
||||
|
@ -549,13 +550,22 @@ gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere )
|
|||
}
|
||||
|
||||
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. */
|
||||
XP_LOGFF( "nHere: %d; curLocal: %d; a problem?", nHere, curLocal );
|
||||
/* for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { */
|
||||
/* gi->players[ii].isLocal = ii < nHere; */
|
||||
/* } */
|
||||
for ( XP_U16 ii = 0; ii < nTotal; ++ii ) {
|
||||
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
|
||||
|
@ -762,20 +772,21 @@ player_timePenalty( CurGameInfo* gi, XP_U16 playerNum )
|
|||
|
||||
#ifdef DEBUG
|
||||
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_LOGF( " nPlayers: %d", gi->nPlayers );
|
||||
for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) {
|
||||
const LocalPlayer* lp = &gi->players[ii];
|
||||
XP_LOGF( " player[%d]: local: %d; robotIQ: %d; name: %s", ii,
|
||||
lp->isLocal, lp->robotIQ, lp->name );
|
||||
XP_LOGFF( "msg: %s from %s() line %d; addr: %p", msg, func, line, gi );
|
||||
if ( !!gi ) {
|
||||
XP_LOGF( " nPlayers: %d", gi->nPlayers );
|
||||
for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) {
|
||||
const LocalPlayer* lp = &gi->players[ii];
|
||||
XP_LOGF( " player[%d]: local: %d; robotIQ: %d; name: %s", ii,
|
||||
lp->isLocal, lp->robotIQ, lp->name );
|
||||
}
|
||||
XP_LOGF( " forceChannel: %d", gi->forceChannel );
|
||||
XP_LOGF( " serverRole: %d", gi->serverRole );
|
||||
XP_LOGF( " gameID: %d", gi->gameID );
|
||||
XP_LOGF( " dictName: %s", gi->dictName );
|
||||
}
|
||||
XP_LOGF( " forceChannel: %d", gi->forceChannel );
|
||||
XP_LOGF( " serverRole: %d", gi->serverRole );
|
||||
XP_LOGF( " gameID: %d", gi->gameID );
|
||||
XP_LOGF( " dictName: %s", gi->dictName );
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -64,9 +64,11 @@ typedef struct CurGameInfo {
|
|||
} CurGameInfo;
|
||||
|
||||
#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
|
||||
# define game_logGI(gi, msg)
|
||||
# define LOGGI(gi, msg)
|
||||
#endif
|
||||
|
||||
#ifdef CPLUS
|
||||
|
|
|
@ -77,13 +77,7 @@ static void loadPlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream,
|
|||
static void writePlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream,
|
||||
const PlayerCtxt* pc );
|
||||
static XP_U16 model_getRecentPassCount( ModelCtxt* model );
|
||||
static void recordWord( const XP_UCHAR* word, XP_Bool isLegal,
|
||||
const DictionaryCtxt* dict,
|
||||
|
||||
#ifdef XWFEATURE_BOARDWORDS
|
||||
const MoveInfo* movei, XP_U16 start, XP_U16 end,
|
||||
#endif
|
||||
void* clsur );
|
||||
static void recordWord( const WNParams* wnp, void *closure );
|
||||
#ifdef DEBUG
|
||||
typedef struct _DiffTurnState {
|
||||
XP_S16 lastPlayerNum;
|
||||
|
@ -908,6 +902,8 @@ model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, XP_U16* turn )
|
|||
stack_popEntry( stack, &entry );
|
||||
XP_ASSERT( entry.moveType == MOVE_TYPE );
|
||||
|
||||
model_resetCurrentTurn( model, entry.playerNum );
|
||||
|
||||
replaceNewTiles( model, pool, entry.playerNum, &entry.u.move.newTiles );
|
||||
XP_ASSERT( !model->vol.gi->inDuplicateMode );
|
||||
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
|
||||
|
||||
void
|
||||
|
@ -1562,6 +1572,9 @@ model_setBlankValue( ModelCtxt* model, XP_U16 turn,
|
|||
&nUsed, tfaces, tiles );
|
||||
|
||||
pt->tile = tiles[newIndex] | TILE_BLANK_BIT;
|
||||
|
||||
/* force a recalc in case phonies==PHONIES_BLOCK */
|
||||
invalidateScore( model, turn );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -2465,17 +2478,11 @@ typedef struct _FirstWordData {
|
|||
} FirstWordData;
|
||||
|
||||
static void
|
||||
getFirstWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal),
|
||||
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 )
|
||||
getFirstWord( const WNParams* wnp, void* closure )
|
||||
{
|
||||
FirstWordData* data = (FirstWordData*)closure;
|
||||
if ( '\0' == data->word[0] && '\0' != word[0] ) {
|
||||
XP_STRCAT( data->word, word );
|
||||
if ( '\0' == data->word[0] && '\0' != wnp->word[0] ) {
|
||||
XP_STRCAT( data->word, wnp->word );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2560,17 +2567,10 @@ appendWithCR( XWStreamCtxt* stream, const XP_UCHAR* word, XP_U16* counter )
|
|||
}
|
||||
|
||||
static void
|
||||
recordWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal),
|
||||
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 )
|
||||
|
||||
recordWord( const WNParams* wnp, void* closure )
|
||||
{
|
||||
RecordWordsInfo* info = (RecordWordsInfo*)closure;
|
||||
appendWithCR( info->stream, word, &info->nWords );
|
||||
appendWithCR( info->stream, wnp->word, &info->nWords );
|
||||
}
|
||||
|
||||
WordNotifierInfo*
|
||||
|
@ -2591,32 +2591,31 @@ typedef struct _ListWordsThroughInfo {
|
|||
} ListWordsThroughInfo;
|
||||
|
||||
static void
|
||||
listWordsThrough( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal),
|
||||
const DictionaryCtxt* XP_UNUSED(dict),
|
||||
const MoveInfo* movei, XP_U16 start, XP_U16 end,
|
||||
void* closure )
|
||||
listWordsThrough( const WNParams* wnp, void* closure )
|
||||
{
|
||||
ListWordsThroughInfo* info = (ListWordsThroughInfo*)closure;
|
||||
const MoveInfo* movei = wnp->movei;
|
||||
|
||||
XP_Bool contained = XP_FALSE;
|
||||
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 ) {
|
||||
contained = start <= info->row && end >= info->row;
|
||||
contained = wnp->start <= info->row && wnp->end >= info->row;
|
||||
}
|
||||
|
||||
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}.
|
||||
*
|
||||
* 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,
|
||||
XP_S16 turn, XWStreamCtxt* stream )
|
||||
{
|
||||
XP_Bool found = XP_FALSE;
|
||||
ModelCtxt* tmpModel = makeTmpModel( model, NULL, NULL, NULL, NULL );
|
||||
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,
|
||||
NULL, NULL, NULL );
|
||||
}
|
||||
XP_LOGFF( "nWords: %d", lwtInfo.nWords );
|
||||
found = 0 < lwtInfo.nWords;
|
||||
}
|
||||
|
||||
model_destroy( tmpModel );
|
||||
return found;
|
||||
} /* model_listWordsThrough */
|
||||
#endif
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ extern "C" {
|
|||
#define MAX_NUM_BLANKS 4
|
||||
|
||||
/* 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) */
|
||||
Tile tile; /* 6 bits will do */
|
||||
} MoveInfoTile;
|
||||
|
@ -237,6 +237,7 @@ void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum,
|
|||
|
||||
#ifdef DEBUG
|
||||
void juggleMoveIfDebug( MoveInfo* move );
|
||||
void reverseTiles( MoveInfo* move );
|
||||
void model_dumpSelf( const ModelCtxt* model, const XP_UCHAR* msg );
|
||||
#else
|
||||
# define juggleMoveIfDebug(newMove)
|
||||
|
@ -282,13 +283,18 @@ void model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts,
|
|||
|
||||
/********************* scoring ********************/
|
||||
|
||||
typedef void (*WordNotifierProc)( const XP_UCHAR* word, XP_Bool isLegal,
|
||||
const DictionaryCtxt* dict,
|
||||
typedef struct _WNParams {
|
||||
const XP_UCHAR* word;
|
||||
XP_Bool isLegal;
|
||||
const DictionaryCtxt* dict;
|
||||
#ifdef XWFEATURE_BOARDWORDS
|
||||
const MoveInfo* movei, XP_U16 start,
|
||||
XP_U16 end,
|
||||
const MoveInfo* movei;
|
||||
XP_U16 start;
|
||||
XP_U16 end;
|
||||
#endif
|
||||
void* closure );
|
||||
} WNParams;
|
||||
|
||||
typedef void (*WordNotifierProc)( const WNParams* wnp, void* closure );
|
||||
typedef struct WordNotifierInfo {
|
||||
WordNotifierProc proc;
|
||||
void* closure;
|
||||
|
@ -302,8 +308,8 @@ XP_S16 model_getPlayerScore( ModelCtxt* model, XP_S16 player );
|
|||
XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player,
|
||||
LastMoveInfo* info );
|
||||
#ifdef XWFEATURE_BOARDWORDS
|
||||
void model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row,
|
||||
XP_S16 turn, XWStreamCtxt* stream );
|
||||
XP_Bool model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row,
|
||||
XP_S16 turn, XWStreamCtxt* stream );
|
||||
#endif
|
||||
|
||||
/* Have there been too many passes (so game should end)? */
|
||||
|
|
|
@ -217,6 +217,24 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP,
|
|||
}
|
||||
} /* 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.
|
||||
* Negative score means illegal.
|
||||
*/
|
||||
|
@ -238,17 +256,39 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine,
|
|||
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;
|
||||
|
||||
normalizeMoves( model, turn, isHorizontal, &moveInfo );
|
||||
|
||||
if ( isLegalMove( model, &moveInfo, silent ) ) {
|
||||
score = figureMoveScore( model, turn, &moveInfo, engine, stream,
|
||||
notifyInfo );
|
||||
/* If I'm testing for blocking, I need to chain my test onto any
|
||||
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;
|
||||
} /* checkScoreMove */
|
||||
|
@ -695,11 +735,13 @@ scoreWord( const ModelCtxt* model, XP_U16 turn,
|
|||
XP_UCHAR buf[(MAX_ROWS*2)+1];
|
||||
dict_tilesToString( dict, checkWordBuf, len, buf,
|
||||
sizeof(buf) );
|
||||
(void)(*notifyInfo->proc)( buf, legal, dict,
|
||||
|
||||
WNParams wnp = { .word = buf, .isLegal = legal, .dict = dict,
|
||||
#ifdef XWFEATURE_BOARDWORDS
|
||||
movei, start, end,
|
||||
.movei = movei, .start = start, .end = end,
|
||||
#endif
|
||||
notifyInfo->closure );
|
||||
};
|
||||
(void)(*notifyInfo->proc)( &wnp, notifyInfo->closure );
|
||||
}
|
||||
|
||||
if ( !!stream ) {
|
||||
|
|
|
@ -33,14 +33,14 @@
|
|||
|
||||
void
|
||||
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) );
|
||||
nli->gameID = gi->gameID;
|
||||
XP_STRCAT( nli->dict, gi->dictName );
|
||||
nli->lang = gi->dictLang;
|
||||
nli->nPlayersT = gi->nPlayers;
|
||||
nli->nPlayersH = nPlayers;
|
||||
nli->nPlayersH = nPlayersH;
|
||||
nli->forceChannel = forceChannel;
|
||||
nli->inDuplicateMode = gi->inDuplicateMode;
|
||||
|
||||
|
@ -89,6 +89,7 @@ nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID )
|
|||
void
|
||||
nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream )
|
||||
{
|
||||
LOGNLI( nli );
|
||||
stream_putU8( stream, NLI_VERSION );
|
||||
|
||||
stream_putU16( stream, nli->_conTypes );
|
||||
|
@ -172,6 +173,7 @@ nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream )
|
|||
XP_ASSERT( 0 == stream_getSize( stream ) );
|
||||
|
||||
LOG_RETURNF( "%s", boolToStr(success) );
|
||||
LOGNLI( nli );
|
||||
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
|
||||
|
|
|
@ -80,5 +80,13 @@ void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID );
|
|||
void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID );
|
||||
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
|
||||
|
|
|
@ -231,7 +231,8 @@ pool_containsTiles( const PoolContext* pool, const TrayTileSet* tiles )
|
|||
XP_U16
|
||||
pool_getNTilesLeft( const PoolContext* pool )
|
||||
{
|
||||
return pool->numTilesLeft;
|
||||
XP_ASSERT( !!pool );
|
||||
return NULL == pool ? 0 : pool->numTilesLeft;
|
||||
} /* pool_remainingTileCount */
|
||||
|
||||
XP_U16
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* -*- 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.
|
||||
*
|
||||
* 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 robotTradePct;
|
||||
#endif
|
||||
XP_U16 makePhonyPct;
|
||||
|
||||
RemoteAddress addresses[MAX_NUM_PLAYERS];
|
||||
XWStreamCtxt* prevMoveStream; /* save it to print later */
|
||||
|
@ -122,9 +123,7 @@ struct ServerCtxt {
|
|||
PoolContext* pool;
|
||||
|
||||
BadWordInfo illegalWordInfo;
|
||||
#ifndef XWFEATURE_STANDALONE_ONLY
|
||||
XP_U16 lastMoveSource;
|
||||
#endif
|
||||
|
||||
ServerPlayer players[MAX_NUM_PLAYERS];
|
||||
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 endGameInternal( ServerCtxt* server, GameEndReason why, XP_S16 quitter );
|
||||
static void badWordMoveUndoAndTellUser( ServerCtxt* server,
|
||||
BadWordInfo* bwi );
|
||||
static void badWordMoveUndoAndTellUser( ServerCtxt* server, BadWordInfo* bwi );
|
||||
static XP_Bool tileCountsOk( const ServerCtxt* server );
|
||||
static void setTurn( ServerCtxt* server, XP_S16 turn );
|
||||
static XWStreamCtxt* mkServerStream( ServerCtxt* server );
|
||||
|
@ -202,6 +200,7 @@ static void writeProto( const ServerCtxt* server, XWStreamCtxt* stream,
|
|||
#endif
|
||||
|
||||
#define PICK_NEXT -1
|
||||
#define PICK_CUR -2
|
||||
|
||||
#if defined DEBUG && ! defined XWFEATURE_STANDALONE_ONLY
|
||||
static char*
|
||||
|
@ -226,8 +225,7 @@ getStateStr( XW_State st )
|
|||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
//def DEBUG
|
||||
#ifdef DEBUG
|
||||
static void
|
||||
logNewState( XW_State old, XW_State newst, const char* caller )
|
||||
{
|
||||
|
@ -237,10 +235,10 @@ logNewState( XW_State old, XW_State newst, const char* caller )
|
|||
XP_LOGFF( "state transition %s => %s (from %s())", oldStr, newStr, caller );
|
||||
}
|
||||
}
|
||||
# define SETSTATE( s, st ) { \
|
||||
XW_State old = (s)->nv.gameState; \
|
||||
(s)->nv.gameState = (st); \
|
||||
logNewState( old, st, __func__); \
|
||||
# define SETSTATE( server, st ) { \
|
||||
XW_State old = (server)->nv.gameState; \
|
||||
(server)->nv.gameState = (st); \
|
||||
logNewState( old, st, __func__); \
|
||||
}
|
||||
#else
|
||||
# define SETSTATE( s, st ) (s)->nv.gameState = (st)
|
||||
|
@ -404,6 +402,7 @@ putNV( XWStreamCtxt* stream, const ServerNonvolatiles* nv, XP_U16 nPlayers )
|
|||
stream_putBits( stream, XWSTATE_NBITS, nv->stateAfterShow );
|
||||
|
||||
/* +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->quitter+1 );
|
||||
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 );
|
||||
#else
|
||||
stream_putBits( stream, 2, 0 );
|
||||
#endif
|
||||
|
||||
writeStreamIf( stream, server->nv.prevMoveStream );
|
||||
writeStreamIf( stream, server->nv.prevWordsStream );
|
||||
|
@ -630,6 +625,7 @@ server_prefsChanged( ServerCtxt* server, const CommonPrefs* cp )
|
|||
server->nv.robotThinkMax = cp->robotThinkMax;
|
||||
server->nv.robotTradePct = cp->robotTradePct;
|
||||
#endif
|
||||
server->nv.makePhonyPct = cp->makePhonyPct;
|
||||
} /* server_prefsChanged */
|
||||
|
||||
XP_S16
|
||||
|
@ -672,8 +668,9 @@ server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream )
|
|||
|
||||
nPlayers = gi->nPlayers;
|
||||
XP_ASSERT( nPlayers > 0 );
|
||||
stream_putBits( stream, NPLAYERS_NBITS,
|
||||
gi_countLocalPlayers( gi, XP_FALSE) );
|
||||
XP_U16 localPlayers = gi_countLocalPlayers( gi, XP_FALSE);
|
||||
XP_ASSERT( 0 < localPlayers );
|
||||
stream_putBits( stream, NPLAYERS_NBITS, localPlayers );
|
||||
|
||||
for ( lp = gi->players; nPlayers-- > 0; ++lp ) {
|
||||
XP_UCHAR* name;
|
||||
|
@ -1373,6 +1370,11 @@ makeRobotMove( ServerCtxt* server )
|
|||
/* if canMove is false, this is a fake move, a pass */
|
||||
|
||||
if ( canMove || NPASSES_OK(server) ) {
|
||||
#ifdef DEBUG
|
||||
if ( server->nv.makePhonyPct > XP_RANDOM() % 100 ) {
|
||||
reverseTiles( &newMove );
|
||||
}
|
||||
#endif
|
||||
juggleMoveIfDebug( &newMove );
|
||||
model_makeTurnFromMoveInfo( model, turn, &newMove );
|
||||
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
|
||||
sendBadWordMsgs( server );
|
||||
#endif
|
||||
nextTurn( server, PICK_NEXT ); /* sets server->nv.gameState */
|
||||
nextTurn( server, PICK_NEXT );
|
||||
//moreToDo = XP_TRUE; /* why? */
|
||||
break;
|
||||
|
||||
|
@ -1605,7 +1607,7 @@ server_do( ServerCtxt* server )
|
|||
|
||||
case XWSTATE_MOVE_CONFIRM_MUSTSEND:
|
||||
XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER );
|
||||
tellMoveWasLegal( server );
|
||||
tellMoveWasLegal( server ); /* sets state */
|
||||
nextTurn( server, PICK_NEXT );
|
||||
break;
|
||||
|
||||
|
@ -2049,14 +2051,14 @@ static void
|
|||
bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi )
|
||||
{
|
||||
XP_U16 nWords = stream_getBits( stream, 4 );
|
||||
const XP_UCHAR** sp = bwi->words;
|
||||
|
||||
bwi->nWords = nWords;
|
||||
bwi->dictName = ( STREAM_VERS_DICTNAME <= stream_getVersion( stream ) )
|
||||
? stringFromStream( mpool, stream ) : NULL;
|
||||
for ( sp = bwi->words; nWords; ++sp, --nWords ) {
|
||||
*sp = (const XP_UCHAR*)stringFromStream( mpool, stream );
|
||||
for ( int ii = 0; ii < nWords; ++ii ) {
|
||||
bwi->words[ii] = (const XP_UCHAR*)stringFromStream( mpool, stream );
|
||||
}
|
||||
bwi->words[nWords] = NULL;
|
||||
} /* bwiFromStream */
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -2121,10 +2123,15 @@ sendBadWordMsgs( ServerCtxt* server )
|
|||
|
||||
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 );
|
||||
|
||||
freeBWI( MPPARM(server->mpool) &server->illegalWordInfo );
|
||||
}
|
||||
SETSTATE( server, XWSTATE_INTURN );
|
||||
} /* sendBadWordMsgs */
|
||||
#endif
|
||||
|
||||
|
@ -2512,18 +2519,19 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn )
|
|||
{
|
||||
XP_LOGFF( "(nxtTurn=%d)", nxtTurn );
|
||||
CurGameInfo* gi = server->vol.gi;
|
||||
XP_Bool playerTilesLeft = XP_FALSE;
|
||||
XP_S16 currentTurn = server->nv.currentTurn;
|
||||
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 ) {
|
||||
XP_LOGFF( "doing nothing; state %s != XWSTATE_INTURN",
|
||||
getStateStr(server->nv.gameState) );
|
||||
XP_ASSERT( !moreToDo );
|
||||
goto exit;
|
||||
} else if ( currentTurn >= 0 ) {
|
||||
playerTilesLeft = tileCountsOk( server );
|
||||
if ( inDuplicateMode(server) ) {
|
||||
nxtTurn = dupe_nextTurn( server );
|
||||
} 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
|
||||
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. */
|
||||
playerTilesLeft = XP_TRUE;
|
||||
XP_ASSERT( nxtTurn == model_getNextTurn( server->vol.model ) );
|
||||
}
|
||||
XP_Bool playerTilesLeft = tileCountsOk( server );
|
||||
SETSTATE( server, XWSTATE_INTURN ); /* even if game over, if undoing */
|
||||
|
||||
if ( playerTilesLeft && NPASSES_OK(server) ){
|
||||
|
@ -2611,24 +2619,18 @@ server_setGameOverListener( ServerCtxt* server, GameOverListener gol,
|
|||
} /* server_setGameOverListener */
|
||||
|
||||
static void
|
||||
storeBadWords( const XP_UCHAR* word, XP_Bool isLegal,
|
||||
const DictionaryCtxt* dict,
|
||||
#ifdef XWFEATURE_BOARDWORDS
|
||||
const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start),
|
||||
XP_U16 XP_UNUSED(end),
|
||||
#endif
|
||||
void* closure )
|
||||
storeBadWords( const WNParams* wnp, void* closure )
|
||||
{
|
||||
if ( !isLegal ) {
|
||||
if ( !wnp->isLegal ) {
|
||||
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 ) {
|
||||
server->illegalWordInfo.dictName = copyString( server->mpool, name );
|
||||
}
|
||||
server->illegalWordInfo.words[server->illegalWordInfo.nWords++]
|
||||
= copyString( server->mpool, word );
|
||||
= copyString( server->mpool, wnp->word );
|
||||
}
|
||||
} /* storeBadWords */
|
||||
|
||||
|
@ -3596,6 +3598,7 @@ finishMove( ServerCtxt* server, TrayTileSet* newTiles, XP_U16 turn )
|
|||
} else if (isClient && (gi->phoniesAction == PHONIES_DISALLOW)
|
||||
&& nTilesMoved > 0 ) {
|
||||
SETSTATE( server, XWSTATE_MOVE_CONFIRM_WAIT );
|
||||
setTurn( server, -1 );
|
||||
#endif
|
||||
} else {
|
||||
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 ( DUP_PLAYER == turn && inDupMode ) {
|
||||
turn = dupe_nextTurn( server );
|
||||
} else if ( PICK_CUR == turn ) {
|
||||
XP_ASSERT( !inDupMode );
|
||||
turn = model_getNextTurn( server->vol.model );
|
||||
} else if ( 0 <= turn && !inDupMode ) {
|
||||
XP_ASSERT( turn == model_getNextTurn( server->vol.model ) );
|
||||
}
|
||||
|
@ -3843,11 +3849,13 @@ setTurn( ServerCtxt* server, XP_S16 turn )
|
|||
static void
|
||||
tellMoveWasLegal( ServerCtxt* server )
|
||||
{
|
||||
XWStreamCtxt* stream;
|
||||
XWStreamCtxt* stream =
|
||||
messageStreamWithHeader( server, server->lastMoveSource,
|
||||
XWPROTO_MOVE_CONFIRM );
|
||||
|
||||
stream = messageStreamWithHeader( server, server->lastMoveSource,
|
||||
XWPROTO_MOVE_CONFIRM );
|
||||
stream_destroy( stream );
|
||||
|
||||
SETSTATE( server, XWSTATE_INTURN );
|
||||
} /* tellMoveWasLegal */
|
||||
|
||||
static XP_Bool
|
||||
|
@ -3862,6 +3870,7 @@ handleIllegalWord( ServerCtxt* server, XWStreamCtxt* incoming )
|
|||
|
||||
freeBWI( MPPARM(server->mpool) &bwi );
|
||||
|
||||
SETSTATE( server, XWSTATE_INTURN );
|
||||
return XP_TRUE;
|
||||
} /* handleIllegalWord */
|
||||
|
||||
|
@ -3872,7 +3881,8 @@ handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) )
|
|||
XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT );
|
||||
XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT );
|
||||
|
||||
nextTurn( server, PICK_NEXT );
|
||||
SETSTATE( server, XWSTATE_INTURN );
|
||||
nextTurn( server, PICK_CUR );
|
||||
|
||||
return accepted;
|
||||
} /* handleMoveOk */
|
||||
|
@ -4120,7 +4130,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming )
|
|||
case XWPROTO_BADWORD_INFO:
|
||||
accepted = handleIllegalWord( server, incoming );
|
||||
if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) {
|
||||
nextTurn( server, PICK_NEXT );
|
||||
nextTurn( server, PICK_CUR );
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -108,6 +108,11 @@ static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex,
|
|||
static void freeForPhone( SMSProto* state, const XP_UCHAR* phone );
|
||||
static void freeMsg( SMSProto* state, MsgRec** msg );
|
||||
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_init( MPFORMAL XW_DUtilCtxt* dutil )
|
||||
|
@ -216,8 +221,8 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID,
|
|||
|
||||
#ifdef DEBUG
|
||||
XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen );
|
||||
XP_LOGF( "%s(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", __func__, cmd,
|
||||
gameID, buflen, checksum, toPhone );
|
||||
XP_LOGFF( "(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", cmd,
|
||||
gameID, buflen, checksum, toPhone );
|
||||
XP_FREEP( state->mpool, &checksum );
|
||||
#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 */
|
||||
XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil );
|
||||
if ( cmd != NONE ) {
|
||||
if ( cmd != NONE && 0 < buflen ) {
|
||||
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;
|
||||
|
||||
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 );
|
||||
|
||||
pthread_mutex_unlock( &state->mutex );
|
||||
logResult( state, result, __func__ );
|
||||
return result;
|
||||
}
|
||||
} /* smsproto_prepOutbound */
|
||||
|
||||
static SMSMsgArray*
|
||||
appendLocMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgLoc* msg )
|
||||
|
@ -292,7 +298,14 @@ SMSMsgArray*
|
|||
smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
|
||||
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;
|
||||
pthread_mutex_lock( &state->mutex );
|
||||
|
||||
|
@ -361,7 +374,8 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
|
|||
|
||||
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 );
|
||||
return result;
|
||||
|
@ -395,6 +409,39 @@ smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr )
|
|||
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
|
||||
freeMsg( SMSProto* XP_UNUSED_DBG(state), MsgRec** msgp )
|
||||
{
|
||||
|
|
|
@ -71,10 +71,11 @@ typedef struct PickInfo {
|
|||
XP_U16 thisPick; /* <= nTotal */
|
||||
} PickInfo;
|
||||
|
||||
typedef struct BadWordInfo {
|
||||
typedef struct _BadWordInfo {
|
||||
XP_U16 nWords;
|
||||
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;
|
||||
|
||||
/* 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 );
|
||||
#endif
|
||||
|
||||
void (*m_util_informWordBlocked)( XW_UtilCtxt* uc, const XP_UCHAR* word,
|
||||
const XP_UCHAR* dictName );
|
||||
|
||||
#ifdef XWFEATURE_SEARCHLIMIT
|
||||
XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc,
|
||||
XP_U16* min, XP_U16* max );
|
||||
|
@ -308,6 +312,9 @@ struct XW_UtilCtxt {
|
|||
# define util_addrChange( uc, addro, addrn )
|
||||
#endif
|
||||
|
||||
#define util_informWordBlocked(uc, w, d) \
|
||||
(uc)->vtable->m_util_informWordBlocked( (uc), (w), (d) )
|
||||
|
||||
#ifdef XWFEATURE_SEARCHLIMIT
|
||||
#define util_getTraySearchLimits(uc,min,max) \
|
||||
(uc)->vtable->m_util_getTraySearchLimits((uc), (min), (max))
|
||||
|
|
45
xwords4/dawg/Hungarian/Makefile
Normal file
45
xwords4/dawg/Hungarian/Makefile
Normal 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
|
50
xwords4/dawg/Hungarian/info.txt
Normal file
50
xwords4/dawg/Hungarian/info.txt
Normal 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>
|
|
@ -220,15 +220,16 @@ def process(args):
|
|||
|
||||
nodes = loadNodes( dawg, nodeSize )
|
||||
words = []
|
||||
expandDAWG( nodes, nodeSize, offset, data, words )
|
||||
assert len(words) == nWords
|
||||
if nodes:
|
||||
expandDAWG( nodes, nodeSize, offset, data, words )
|
||||
assert len(words) == nWords
|
||||
if args.DUMP_WORDS:
|
||||
for word in words:
|
||||
print(word)
|
||||
|
||||
def mkParser():
|
||||
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')
|
||||
parser.add_argument('--dump-words', dest = 'DUMP_WORDS', default = False,
|
||||
action = 'store_true', help = 'write wordlist to stdout')
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "gsrcwrap.h"
|
||||
#include "linuxsms.h"
|
||||
#include "strutils.h"
|
||||
#include "dbgutil.h"
|
||||
|
||||
typedef struct CursesBoardState {
|
||||
CursesAppGlobals* aGlobals;
|
||||
|
@ -590,6 +591,7 @@ initNoDraw( CursesBoardState* cbState, sqlite3_int64 rowid,
|
|||
cGlobals->cp.robotThinkMax = params->robotThinkMax;
|
||||
cGlobals->cp.robotTradePct = params->robotTradePct;
|
||||
#endif
|
||||
cGlobals->cp.makePhonyPct = params->makePhonyPct;
|
||||
|
||||
if ( linuxOpenGame( cGlobals, &result->procs, returnAddr ) ) {
|
||||
result = ref( result );
|
||||
|
@ -640,16 +642,21 @@ findOrOpen( CursesBoardState* cbState, sqlite3_int64 rowid,
|
|||
return result;
|
||||
}
|
||||
|
||||
XP_U16
|
||||
cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid,
|
||||
bool
|
||||
cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, XP_U16 expectSeed,
|
||||
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from )
|
||||
{
|
||||
LOG_FUNC();
|
||||
CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid, NULL, NULL );
|
||||
CommonGlobals* cGlobals = &bGlobals->cGlobals;
|
||||
gameGotBuf( cGlobals, XP_TRUE, buf, len, from );
|
||||
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
|
||||
|
@ -663,7 +670,11 @@ cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
|
|||
getRowsForGameID( params->pDb, gameID, rowids, &nRows );
|
||||
XP_LOGF( "%s(): found %d rows for gameID %d", __func__, nRows, gameID );
|
||||
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
|
||||
|
||||
static void
|
||||
curses_util_notifyIllegalWords( XW_UtilCtxt* uc,
|
||||
BadWordInfo* XP_UNUSED(bwi),
|
||||
XP_U16 XP_UNUSED(player),
|
||||
XP_Bool XP_UNUSED(turnLost) )
|
||||
curses_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi,
|
||||
XP_U16 player, XP_Bool 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;
|
||||
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 */
|
||||
|
||||
/* this needs to change!!! */
|
||||
|
@ -1042,6 +1061,13 @@ curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
|
|||
}
|
||||
#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
|
||||
static XWStreamCtxt*
|
||||
|
@ -1113,6 +1139,7 @@ setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util )
|
|||
#ifdef XWFEATURE_BOARDWORDS
|
||||
SET_PROC(cellSquareHeld);
|
||||
#endif
|
||||
SET_PROC(informWordBlocked);
|
||||
|
||||
#ifdef XWFEATURE_SEARCHLIMIT
|
||||
SET_PROC(getTraySearchLimits);
|
||||
|
@ -1122,7 +1149,7 @@ setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util )
|
|||
#endif
|
||||
#undef SET_PROC
|
||||
|
||||
assertUtilCallbacksSet( util );
|
||||
assertTableFull( util->vtable, sizeof(*util->vtable), "curses util" );
|
||||
} /* setupCursesUtilCallbacks */
|
||||
|
||||
static bool
|
||||
|
@ -1189,10 +1216,12 @@ inviteList( CommonGlobals* cGlobals, CommsAddrRec* addr, GSList* invitees,
|
|||
if ( haveAddressees ) {
|
||||
LaunchParams* params = cGlobals->params;
|
||||
for ( int ii = 0; ii < g_slist_length(invitees); ++ii ) {
|
||||
const XP_U16 nPlayers = 1;
|
||||
gint forceChannel = ii + 1;
|
||||
const XP_U16 nPlayersH = params->connInfo.inviteeCounts[ii];
|
||||
const gint forceChannel = ii + 1;
|
||||
XP_LOGFF( "using nPlayersH of %d, forceChannel of %d for guest device %d",
|
||||
nPlayersH, forceChannel, ii );
|
||||
NetLaunchInfo nli = {0};
|
||||
nli_init( &nli, cGlobals->gi, addr, nPlayers, forceChannel );
|
||||
nli_init( &nli, cGlobals->gi, addr, nPlayersH, forceChannel );
|
||||
if ( useRelay ) {
|
||||
uint64_t inviteeRelayID = (uint64_t)g_slist_nth_data( invitees, ii );
|
||||
relaycon_invite( params, (XP_U32)inviteeRelayID, NULL, &nli );
|
||||
|
@ -1217,8 +1246,8 @@ handleInvite( void* closure, int XP_UNUSED(key) )
|
|||
XP_ASSERT( comms );
|
||||
comms_getAddr( comms, &addr );
|
||||
|
||||
XP_U16 nPlayers = 1;
|
||||
gint forceChannel = 1;
|
||||
const XP_U16 nPlayers = params->connInfo.inviteeCounts[forceChannel-1];
|
||||
NetLaunchInfo nli = {0};
|
||||
nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel );
|
||||
|
||||
|
|
|
@ -44,8 +44,9 @@ bool cb_new( CursesBoardState* cbState, const cb_dims* dims );
|
|||
void cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
|
||||
const CommsAddrRec* returnAddr, const cb_dims* dims );
|
||||
|
||||
XP_U16 cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid,
|
||||
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from );
|
||||
bool cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid,
|
||||
XP_U16 expectSeed, const XP_U8* buf, XP_U16 len,
|
||||
const CommsAddrRec* from );
|
||||
void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
|
||||
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from );
|
||||
void cb_closeAll( CursesBoardState* cbState );
|
||||
|
|
|
@ -1201,9 +1201,7 @@ cursesGotBuf( void* closure, const CommsAddrRec* addr,
|
|||
rowidFromToken( XP_NTOHL( clientToken ), &rowid, &gotSeed );
|
||||
|
||||
/* 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 );
|
||||
XP_ASSERT( seed == 0 || gotSeed == seed );
|
||||
XP_USE( seed );
|
||||
cb_feedRow( aGlobals->cbState, rowid, gotSeed, buf, len, addr );
|
||||
|
||||
/* if ( seed == comms_getChannelSeed( cGlobals->game.comms ) ) { */
|
||||
/* gameGotBuf( cGlobals, XP_TRUE, buf, len, addr ); */
|
||||
|
|
|
@ -23,28 +23,135 @@
|
|||
#include "gtkdraw.h"
|
||||
#include "linuxutl.h"
|
||||
#include "main.h"
|
||||
#include "dbgutil.h"
|
||||
|
||||
#define SNAP_WIDTH 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,
|
||||
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
|
||||
static char* sqliteErr2str( int err );
|
||||
#endif
|
||||
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*
|
||||
openGamesDB( const char* dbName )
|
||||
{
|
||||
int result = sqlite3_initialize();
|
||||
#ifdef DEBUG
|
||||
int result =
|
||||
#endif
|
||||
sqlite3_initialize();
|
||||
XP_ASSERT( SQLITE_OK == result );
|
||||
|
||||
sqlite3* pDb = NULL;
|
||||
result = sqlite3_open( dbName, &pDb );
|
||||
#ifdef DEBUG
|
||||
result =
|
||||
#endif
|
||||
sqlite3_open( dbName, &pDb );
|
||||
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 =
|
||||
"CREATE TABLE games ( "
|
||||
"rowid INTEGER PRIMARY KEY AUTOINCREMENT"
|
||||
|
@ -59,25 +166,17 @@ openGamesDB( const char* dbName )
|
|||
",local INT(1)"
|
||||
",nmoves INT"
|
||||
",seed INT"
|
||||
",nPending INT"
|
||||
",role INT"
|
||||
",dictlang INT"
|
||||
",gameid INT"
|
||||
",ntotal INT(2)"
|
||||
",nmissing INT(2)"
|
||||
",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 =
|
||||
"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;
|
||||
db_storeInt( pDb, KEY_DB_VERSION, CUR_DB_VERSION );
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -558,30 +657,46 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
|
|||
XP_ASSERT( !!pDb );
|
||||
char query[256];
|
||||
snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid );
|
||||
sqlite3_stmt* ppStmt;
|
||||
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 );
|
||||
execNoResult( pDb, query, false );
|
||||
}
|
||||
|
||||
void
|
||||
db_store( sqlite3* pDb, const gchar* key, const gchar* value )
|
||||
{
|
||||
XP_ASSERT( !!pDb );
|
||||
gchar* buf =
|
||||
gchar* query =
|
||||
g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
|
||||
key, value );
|
||||
sqlite3_stmt *ppStmt;
|
||||
int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL );
|
||||
assertPrintResult( pDb, result, SQLITE_OK );
|
||||
result = sqlite3_step( ppStmt );
|
||||
assertPrintResult( pDb, result, SQLITE_DONE );
|
||||
XP_USE( result );
|
||||
sqlite3_finalize( ppStmt );
|
||||
g_free( buf );
|
||||
execNoResult( pDb, query, false );
|
||||
g_free( query );
|
||||
}
|
||||
|
||||
static bool
|
||||
db_fetchInt( sqlite3* pDb, const gchar* key, int32_t* resultP )
|
||||
{
|
||||
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
|
||||
|
@ -632,13 +747,7 @@ db_remove( sqlite3* pDb, const gchar* key )
|
|||
XP_ASSERT( !!pDb );
|
||||
char query[256];
|
||||
snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key );
|
||||
sqlite3_stmt *ppStmt;
|
||||
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 );
|
||||
execNoResult( pDb, query, false );
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
|
@ -656,6 +765,21 @@ getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int *len )
|
|||
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
|
||||
# define CASESTR(c) case c: return #c
|
||||
static char*
|
||||
|
@ -704,7 +828,7 @@ assertPrintResult( sqlite3* pDb, int XP_UNUSED_DBG(result), int expect )
|
|||
int code = sqlite3_errcode( pDb );
|
||||
XP_ASSERT( code == result ); /* do I need to pass it? */
|
||||
if ( code != expect ) {
|
||||
XP_LOGFF( "sqlite3 error: %s", sqlite3_errmsg( pDb ) );
|
||||
XP_LOGFF( "sqlite3 error: %d (%s)", code, sqlite3_errmsg( pDb ) );
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,9 +46,9 @@ gtkask( GtkWidget* parent, const gchar *message, GtkButtonsType buttons,
|
|||
}
|
||||
|
||||
gint
|
||||
gtkask_timeout( GtkWidget* parent, const gchar *message,
|
||||
gtkask_timeout( GtkWidget* parent, const gchar* message,
|
||||
GtkButtonsType buttons, const AskPair* buttxts,
|
||||
XP_U16 timeout )
|
||||
uint32_t timeoutMS )
|
||||
{
|
||||
guint src = 0;
|
||||
GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)parent,
|
||||
|
@ -56,9 +56,9 @@ gtkask_timeout( GtkWidget* parent, const gchar *message,
|
|||
GTK_DIALOG_MODAL,
|
||||
buttons, "%s", message );
|
||||
|
||||
if ( timeout > 0 ) {
|
||||
XP_LOGF( "%s(%s)", __func__, message ); /* log since times out... */
|
||||
src = g_timeout_add( timeout, timer_func, dlg );
|
||||
if ( timeoutMS > 0 ) {
|
||||
XP_LOGF( "%s(\"%s\")", __func__, message ); /* log since times out... */
|
||||
src = g_timeout_add( timeoutMS, timer_func, dlg );
|
||||
}
|
||||
|
||||
while ( !!buttxts && !!buttxts->txt ) {
|
||||
|
|
|
@ -38,7 +38,7 @@ gint gtkask( GtkWidget* parent, const gchar *message,
|
|||
GtkButtonsType buttons, const AskPair* buttxts );
|
||||
gint gtkask_timeout( GtkWidget* parent, const gchar *message,
|
||||
GtkButtonsType buttons, const AskPair* buttxts,
|
||||
XP_U16 timeout );
|
||||
uint32_t timeoutMS );
|
||||
|
||||
#endif
|
||||
#endif /* PLATFORM_GTK */
|
||||
|
|
|
@ -583,7 +583,8 @@ createOrLoadObjects( GtkGameGlobals* globals )
|
|||
|
||||
TransportProcs procs;
|
||||
setTransportProcs( &procs, globals );
|
||||
if ( linuxOpenGame( cGlobals, &procs, NULL ) ) {
|
||||
|
||||
if ( linuxOpenGame( cGlobals, &procs, &cGlobals->addr ) ) {
|
||||
|
||||
if ( !params->fileName && !!params->dbName ) {
|
||||
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_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 );
|
||||
|
||||
if ( cGlobals->params->skipWarnings ) {
|
||||
|
@ -1961,9 +1962,10 @@ gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc),
|
|||
static void
|
||||
gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus )
|
||||
{
|
||||
LOG_FUNC();
|
||||
XP_USE( uc );
|
||||
XP_USE( bonus );
|
||||
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
|
||||
gchar* msg = g_strdup_printf( "bonusSquareHeld(bonus=%d)", bonus );
|
||||
gtkask_timeout( globals->window, msg, GTK_BUTTONS_OK, NULL, 1000 );
|
||||
g_free( msg );
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1987,12 +1989,24 @@ gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player )
|
|||
static void
|
||||
gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
|
||||
{
|
||||
XP_USE( uc );
|
||||
catOnClose( words, NULL );
|
||||
fprintf( stderr, "\n" );
|
||||
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
|
||||
const XP_U8* bytes = stream_getPtr( words );
|
||||
gchar* msg = g_strdup_printf( "words for lookup:\n%s",
|
||||
(XP_UCHAR*)bytes );
|
||||
gtktell( globals->window, msg );
|
||||
g_free( msg );
|
||||
}
|
||||
#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
|
||||
gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id )
|
||||
{
|
||||
|
@ -2225,10 +2239,10 @@ setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util )
|
|||
#ifdef XWFEATURE_BOARDWORDS
|
||||
SET_PROC(cellSquareHeld);
|
||||
#endif
|
||||
|
||||
SET_PROC(informWordBlocked);
|
||||
#undef SET_PROC
|
||||
|
||||
assertUtilCallbacksSet( util );
|
||||
assertTableFull( util->vtable, sizeof(*util->vtable), "gtk util" );
|
||||
} /* setupGtkUtilCallbacks */
|
||||
|
||||
#ifndef XWFEATURE_STANDALONE_ONLY
|
||||
|
@ -2358,6 +2372,7 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params,
|
|||
cGlobals->cp.robotThinkMax = params->robotThinkMax;
|
||||
cGlobals->cp.robotTradePct = params->robotTradePct;
|
||||
#endif
|
||||
cGlobals->cp.makePhonyPct = params->makePhonyPct;
|
||||
#ifdef XWFEATURE_CROSSHAIRS
|
||||
cGlobals->cp.hideCrosshairs = params->hideCrosshairs;
|
||||
#endif
|
||||
|
|
|
@ -779,7 +779,7 @@ gtkDrawTileImpl( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP,
|
|||
formatRect.width -= 6;
|
||||
|
||||
if ( notEmpty ) {
|
||||
if ( !!bitmaps ) {
|
||||
if ( !!bitmaps && !!bitmaps->bmps[1] ) {
|
||||
drawBitmapFromLBS( dctx, bitmaps->bmps[1], &insetR );
|
||||
} else if ( !!textP ) {
|
||||
if ( *textP != LETTER_NONE ) { /* blank */
|
||||
|
|
|
@ -24,18 +24,21 @@
|
|||
#include "gtkask.h"
|
||||
|
||||
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;
|
||||
*whichSet = XP_TRUE;
|
||||
gtk_main_quit();
|
||||
|
||||
GtkWidget* dialog = gtk_widget_get_toplevel( widget );
|
||||
gtk_dialog_response( GTK_DIALOG(dialog), 1000 );
|
||||
} /* button_event */
|
||||
|
||||
#ifdef FEATURE_TRAY_EDIT
|
||||
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 */
|
||||
#endif
|
||||
|
||||
|
@ -51,7 +54,6 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name,
|
|||
GtkWidget* vbox;
|
||||
GtkWidget* hbox = NULL;
|
||||
char* txt;
|
||||
XP_S16 ii;
|
||||
GtkWidget* button;
|
||||
XP_UCHAR buf[64];
|
||||
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 );
|
||||
|
||||
for ( ii = 0; ii < nTiles; ++ii ) {
|
||||
for ( int ii = 0; ii < nTiles; ++ii ) {
|
||||
|
||||
if ( ii % BUTTONS_PER_ROW == 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];
|
||||
int len = snprintf( curTilesBuf, sizeof(curTilesBuf), "%s",
|
||||
"Tiles so far: " );
|
||||
for ( ii = 0; ii < curPick->nTiles; ++ii ) {
|
||||
for ( int ii = 0; ii < curPick->nTiles; ++ii ) {
|
||||
Tile tile = curPick->tiles[ii];
|
||||
len += snprintf( &curTilesBuf[len], sizeof(curTilesBuf) - len, "%s ",
|
||||
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_widget_show_all( dialog );
|
||||
|
||||
gtk_dialog_run( GTK_DIALOG( dialog ) );
|
||||
// gint dlgResult =
|
||||
(void)gtk_dialog_run( GTK_DIALOG( dialog ) );
|
||||
|
||||
gtk_widget_destroy( dialog );
|
||||
|
||||
XP_S16 result;
|
||||
if ( backedUp ) {
|
||||
ii = PICKER_BACKUP;
|
||||
result = PICKER_BACKUP;
|
||||
} else {
|
||||
for ( ii = 0; ii < nTiles; ++ii ) {
|
||||
result = PICKER_PICKALL;
|
||||
for ( int ii = 0; ii < nTiles; ++ii ) {
|
||||
if ( results[ii] ) {
|
||||
result = ii;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( ii == nTiles ) {
|
||||
ii = PICKER_PICKALL;
|
||||
}
|
||||
}
|
||||
|
||||
return ii;
|
||||
return result;
|
||||
} /* gtkletterask */
|
||||
|
||||
#endif /* PLATFORM_GTK */
|
||||
|
|
|
@ -218,7 +218,7 @@ addPhoniesCombo( GtkNewGameState* state, GtkWidget* parent )
|
|||
FALSE, TRUE, 0 );
|
||||
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 ) {
|
||||
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_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_box_pack_start( GTK_BOX(hbox), phoniesCombo, FALSE, TRUE, 0 );
|
||||
gtk_widget_show( hbox );
|
||||
|
|
|
@ -329,7 +329,7 @@ linuxOpenGame( CommonGlobals* cGlobals, const TransportProcs* procs,
|
|||
cGlobals->gi->allowHintRect = params->allowHintRect;
|
||||
#endif
|
||||
|
||||
if ( params->needsNewGame ) {
|
||||
if ( params->needsNewGame && !opened ) {
|
||||
XP_ASSERT(0);
|
||||
// new_game_impl( globals, XP_FALSE );
|
||||
}
|
||||
|
@ -881,6 +881,7 @@ typedef enum {
|
|||
,CMD_INVITEE_SMSNUMBER
|
||||
,CMD_SMSPORT
|
||||
#endif
|
||||
,CMD_INVITEE_COUNTS
|
||||
#ifdef XWFEATURE_RELAY
|
||||
,CMD_ROOMNAME
|
||||
,CMD_ADVERTISEROOM
|
||||
|
@ -896,6 +897,7 @@ typedef enum {
|
|||
,CMD_SLOWROBOT
|
||||
,CMD_TRADEPCT
|
||||
#endif
|
||||
,CMD_MAKE_PHONY_PCT
|
||||
#ifdef USE_GLIBLOOP /* just because hard to implement otherwise */
|
||||
,CMD_UNDOPCT
|
||||
#endif
|
||||
|
@ -1013,12 +1015,15 @@ static CmdInfoRec CmdInfoRecs[] = {
|
|||
,{ CMD_INVITEE_SMSNUMBER, true, "invitee-sms-number", "number to send any invitation to" }
|
||||
,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" }
|
||||
#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
|
||||
,{ CMD_ROOMNAME, true, "room", "name of room on relay" }
|
||||
,{ CMD_ADVERTISEROOM, false, "make-public", "make room public on relay" }
|
||||
,{ CMD_JOINADVERTISED, false, "join-public", "look for a public room" }
|
||||
,{ 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",
|
||||
"provides bonus info: . + * ^ and ! are legal" }
|
||||
,{ 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_TRADEPCT, true, "trade-pct", "what pct of the time should robot trade" }
|
||||
#endif
|
||||
,{ CMD_MAKE_PHONY_PCT, true, "make-phony-pct",
|
||||
"what pct of the time should robot play a bad word" }
|
||||
#ifdef USE_GLIBLOOP
|
||||
,{ CMD_UNDOPCT, true, "undo-pct",
|
||||
"each second, what are the odds of doing an undo" }
|
||||
|
@ -2388,20 +2395,6 @@ setupLinuxUtilCallbacks( XW_UtilCtxt* util )
|
|||
#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
|
||||
assertDrawCallbacksSet( const DrawCtxVTable* vtable )
|
||||
{
|
||||
|
@ -2548,6 +2541,9 @@ main( int argc, char** argv )
|
|||
initParams( &mainParams );
|
||||
|
||||
/* defaults */
|
||||
for ( int ii = 0; ii < VSIZE(mainParams.connInfo.inviteeCounts); ++ii ) {
|
||||
mainParams.connInfo.inviteeCounts[ii] = 1;
|
||||
}
|
||||
#ifdef XWFEATURE_RELAY
|
||||
mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT;
|
||||
mainParams.connInfo.relay.relayName = "localhost";
|
||||
|
@ -2748,6 +2744,16 @@ main( int argc, char** argv )
|
|||
g_slist_append( mainParams.connInfo.sms.inviteePhones, optarg );
|
||||
addr_addType( &mainParams.addr, COMMS_CONN_SMS );
|
||||
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:
|
||||
mainParams.connInfo.sms.port = atoi(optarg);
|
||||
addr_addType( &mainParams.addr, COMMS_CONN_SMS );
|
||||
|
@ -2837,8 +2843,11 @@ main( int argc, char** argv )
|
|||
case 2:
|
||||
mainParams.pgi.phoniesAction = PHONIES_DISALLOW;
|
||||
break;
|
||||
case 3:
|
||||
mainParams.pgi.phoniesAction = PHONIES_BLOCK;
|
||||
break;
|
||||
default:
|
||||
usage( argv[0], "phonies takes 0 or 1 or 2" );
|
||||
usage( argv[0], "phonies takes 0 or 1 or 2 or 3" );
|
||||
}
|
||||
break;
|
||||
case CMD_BONUSFILE:
|
||||
|
@ -2955,6 +2964,13 @@ main( int argc, char** argv )
|
|||
usage(argv[0], "must be 0 <= n <= 100" );
|
||||
}
|
||||
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
|
||||
|
||||
#ifdef USE_GLIBLOOP
|
||||
|
|
|
@ -226,6 +226,20 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
|
|||
return nRead;
|
||||
} /* 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
|
||||
parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
|
||||
CommsAddrRec* addr )
|
||||
|
@ -244,9 +258,12 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
|
|||
msg->gameID,
|
||||
msg->data, msg->len );
|
||||
break;
|
||||
case INVITE:
|
||||
case INVITE: {
|
||||
NetLaunchInfo nli = {0};
|
||||
nliFromData( params, msg, &nli );
|
||||
(*storage->procs->inviteReceived)( storage->procClosure,
|
||||
(NetLaunchInfo*)msg->data, addr );
|
||||
&nli, addr );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
XP_ASSERT(0); /* implement me!! */
|
||||
|
@ -291,13 +308,21 @@ linux_sms_invite( LaunchParams* params, const NetLaunchInfo* nli,
|
|||
LOG_FUNC();
|
||||
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;
|
||||
const XP_Bool forceOld = XP_TRUE; /* Send NOW in case test app kills us */
|
||||
SMSMsgArray* arr
|
||||
= smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, nli,
|
||||
sizeof(*nli), toPhone, toPort, XP_FALSE,
|
||||
&waitSecs );
|
||||
= smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, ptr,
|
||||
len, toPhone, toPort, forceOld, &waitSecs );
|
||||
XP_ASSERT( !!arr || !forceOld );
|
||||
sendOrRetry( params, arr, INVITE, waitSecs, toPhone, toPort,
|
||||
nli->gameID, "invite" );
|
||||
stream_destroy( stream );
|
||||
}
|
||||
|
||||
XP_S16
|
||||
|
|
|
@ -129,12 +129,14 @@ typedef struct LaunchParams {
|
|||
XP_U16 robotThinkMin, robotThinkMax;
|
||||
XP_U16 robotTradePct;
|
||||
#endif
|
||||
XP_U16 makePhonyPct;
|
||||
XP_Bool commsDisableds[COMMS_CONN_NTYPES][2];
|
||||
|
||||
DeviceRole serverRole;
|
||||
|
||||
CommsAddrRec addr;
|
||||
struct {
|
||||
XP_U16 inviteeCounts[MAX_NUM_PLAYERS];
|
||||
#ifdef XWFEATURE_RELAY
|
||||
struct {
|
||||
char* relayName;
|
||||
|
|
|
@ -210,12 +210,16 @@ class Device():
|
|||
|
||||
def setApp(self, pct):
|
||||
if self.app == self.args.APP_OLD and not self.app == self.args.APP_NEW:
|
||||
if pct >= random.randint(0, 99):
|
||||
print('launch(): upgrading from ', self.app, ' to ', self.args.APP_NEW)
|
||||
if os.path.exists(self.script) and pct >= random.randint(0, 99):
|
||||
print('launch(): upgrading {} from {} to {}' \
|
||||
.format(self.devName(), self.app, self.args.APP_NEW))
|
||||
self.app = self.args.APP_NEW
|
||||
# nuke script to force regeneration
|
||||
os.unlink(self.script)
|
||||
|
||||
def devName(self):
|
||||
return 'dev_' + str(self.indx)
|
||||
|
||||
def logReaderMain(self):
|
||||
assert self and self.proc
|
||||
# print('logReaderMain called; opening:', self.logPath)
|
||||
|
@ -418,6 +422,8 @@ def build_cmds(args):
|
|||
# make one in three games public
|
||||
usePublic = args.ADD_RELAY and random.randint(0, 3) == 0
|
||||
useDupeMode = random.randint(0, 100) < args.DUP_PCT
|
||||
if args.PHONIES == -1: phonies = GAME % 3
|
||||
else: phonies = args.PHONIES
|
||||
DEV = 0
|
||||
for NLOCALS in LOCALS:
|
||||
DEV += 1
|
||||
|
@ -474,9 +480,15 @@ def build_cmds(args):
|
|||
# it isn't a priority.
|
||||
# PARAMS += ['--seed', args.SEED]
|
||||
|
||||
if DEV == 1: PARAMS += ['--server']
|
||||
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 usePublic: PARAMS += ['--make-public', '--join-public']
|
||||
|
@ -679,7 +691,7 @@ def mkParser():
|
|||
help = 'the app we\'ll upgrade from')
|
||||
parser.add_argument('--start-pct', dest = 'START_PCT', default = 50, type = int,
|
||||
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')
|
||||
|
||||
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,
|
||||
help = 'seconds without change after which to timeout')
|
||||
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',
|
||||
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')
|
||||
|
||||
# #
|
||||
|
|
Loading…
Reference in a new issue