Merge branch 'android_branch' into android_translate

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

View file

@ -1,6 +1,6 @@
def INITIAL_CLIENT_VERS = 9
def 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"

View file

@ -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" />

View file

@ -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

View file

@ -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 );

View file

@ -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;
}

View file

@ -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();
}
} );
}

View file

@ -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" }

View file

@ -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(),

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -62,6 +62,7 @@ import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.LastMoveInfo;
import org.eehouse.android.xw4.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;
}

View file

@ -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() ) {

View file

@ -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;
}
}
}

View file

@ -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,

View file

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

View file

@ -57,6 +57,8 @@ import org.eehouse.android.xw4.DlgDelegate.Action;
public class RelayInviteDelegate extends InviteDelegate {
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 )

View file

@ -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 );
}

View file

@ -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="

View file

@ -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 );
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 ) {

View file

@ -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 );

View file

@ -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 )
{

View file

@ -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,

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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 -->

View file

@ -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>

View file

@ -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"

View file

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

View file

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

View file

@ -30,6 +30,8 @@
void
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

View file

@ -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

View file

@ -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;
}

View file

@ -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,

View file

@ -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():

View file

@ -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 );
}

View file

@ -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 {

View file

@ -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 */

View file

@ -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

View file

@ -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,

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)? */

View file

@ -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 ) {

View file

@ -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

View file

@ -80,5 +80,13 @@ void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID );
void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID );
void nli_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

View file

@ -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

View file

@ -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;

View file

@ -108,6 +108,11 @@ static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex,
static void freeForPhone( SMSProto* state, const XP_UCHAR* phone );
static void 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 )
{

View file

@ -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))

View file

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

View file

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

View file

@ -220,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')

View file

@ -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 );

View file

@ -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 );

View file

@ -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 ); */

View file

@ -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);
}
}

View file

@ -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 ) {

View file

@ -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 */

View file

@ -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

View file

@ -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 */

View file

@ -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 */

View file

@ -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 );

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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')
# #