xwords/xwords4/linux/linuxutl.c
Eric House 3c0a128266 produce, store and display actual snapshot
Done in a way that won't work for Android just to try out the gtk
way. Tweaks and reuses the existing draw context and board, which is
precisely what the android version can't easily do.
2016-08-05 09:42:55 -07:00

736 lines
21 KiB
C

/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2000-2011 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.
*/
#include <stdarg.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h> /* BAD: use glib to support utf8 */
#ifdef DEBUG
# include <execinfo.h> /* for backtrace */
#endif
#include "movestak.h"
#include "linuxutl.h"
#include "main.h"
#include "linuxdict.h"
#include "linuxmain.h"
#include "gamesdb.h"
#include "LocalizedStrIncludes.h"
#ifdef DEBUG
void
linux_debugf( const char* format, ... )
{
char buf[1000];
va_list ap;
struct tm* timp;
struct timeval tv;
struct timezone tz;
gettimeofday( &tv, &tz );
timp = localtime( &tv.tv_sec );
snprintf( buf, sizeof(buf), "<%d>%.2d:%.2d:%.2d:", getpid(),
timp->tm_hour, timp->tm_min, timp->tm_sec );
va_start(ap, format);
vsprintf(buf+strlen(buf), format, ap);
va_end(ap);
fprintf( stderr, "%s\n", buf );
}
void
linux_backtrace( void )
{
void* buffer[128];
int nFound = backtrace( buffer, VSIZE(buffer) );
XP_ASSERT( nFound < VSIZE(buffer) );
char** traces = backtrace_symbols( buffer, nFound );
XP_U16 ii;
for ( ii = 0; ii < nFound; ++ii ) {
XP_LOGF( "trace[%.2d]: %s", ii, traces[ii] );
}
free( traces );
}
#endif
#ifndef MEM_DEBUG
void
linux_freep( void** ptrp )
{
if ( !!*ptrp ) {
free( *ptrp );
*ptrp = NULL;
}
}
#endif
static DictionaryCtxt*
linux_util_makeEmptyDict( XW_UtilCtxt* XP_UNUSED_DBG(uctx) )
{
XP_DEBUGF( "linux_util_makeEmptyDict called" );
return linux_dictionary_make( MPPARM(uctx->mpool) NULL, NULL, XP_FALSE );
} /* linux_util_makeEmptyDict */
#define EM BONUS_NONE
#define DL BONUS_DOUBLE_LETTER
#define DW BONUS_DOUBLE_WORD
#define TL BONUS_TRIPLE_LETTER
#define TW BONUS_TRIPLE_WORD
static XWBonusType*
bonusesFor( XP_U16 boardSize, XP_U16* len )
{
static XWBonusType scrabbleBoard[] = {
TW,//EM,EM,DL,EM,EM,EM,TW,
EM,DW,//EM,EM,EM,TL,EM,EM,
EM,EM,DW,//EM,EM,EM,DL,EM,
DL,EM,EM,DW,//EM,EM,EM,DL,
EM,EM,EM,EM,DW,//EM,EM,EM,
EM,TL,EM,EM,EM,TL,//EM,EM,
EM,EM,DL,EM,EM,EM,DL,//EM,
TW,EM,EM,DL,EM,EM,EM,DW,
}; /* scrabbleBoard */
static XWBonusType seventeen[] = {
TW,//EM,EM,DL,EM,EM,EM,TW,
EM,DW,//EM,EM,EM,TL,EM,EM,
EM,EM,DW,//EM,EM,EM,DL,EM,
DL,EM,EM,DW,//EM,EM,EM,DL,
EM,EM,EM,EM,DW,//EM,EM,EM,
EM,TL,EM,EM,EM,TL,//EM,EM,
EM,EM,DL,EM,EM,EM,DL,//EM,
TW,EM,EM,DL,EM,EM,EM,DW,
TW,EM,EM,DL,EM,EM,EM,DW,DW,
}; /* scrabbleBoard */
XWBonusType* result = NULL;
if ( boardSize == 15 ) {
result = scrabbleBoard;
*len = VSIZE(scrabbleBoard);
} else if ( boardSize == 17 ) {
result = seventeen;
*len = VSIZE(seventeen);
}
return result;
}
#ifdef STREAM_VERS_BIGBOARD
void
setSquareBonuses( const CommonGlobals* cGlobals )
{
XP_U16 nBonuses;
XWBonusType* bonuses =
bonusesFor( cGlobals->gi->boardSize, &nBonuses );
if ( !!bonuses ) {
model_setSquareBonuses( cGlobals->game.model, bonuses, nBonuses );
}
}
#endif
static XWBonusType*
parseBonusFile( XP_U16 nCols, const char* bonusFile )
{
XWBonusType* result = NULL;
FILE* file = fopen( bonusFile, "r" );
if ( !!file ) {
XP_U16 row = 0;
XP_U16 col;
result = malloc( nCols * nCols * sizeof(*result) );
char line[1024];
while ( line == fgets( line, sizeof(line), file ) && row < nCols ) {
bool inComment = false;
char* ch;
XWBonusType bonus;
col = 0;
for ( ch = line; '\0' != *ch; ++ch ) {
switch( *ch ) {
case '#': /* comment; line is done */
inComment = true;
break;
case '+':
bonus = BONUS_DOUBLE_LETTER;
break;
case '*':
bonus = BONUS_DOUBLE_WORD;
break;
case '^':
bonus = BONUS_TRIPLE_LETTER;
break;
case '!':
bonus = BONUS_TRIPLE_WORD;
break;
case '.':
case ' ':
bonus = BONUS_NONE;
break;
case '\n':
case '\a':
break;
default:
if ( !inComment ) {
fprintf( stderr, "unexpected char '%c' in %s\n", *ch, bonusFile );
exit( 1 );
}
break;
}
if ( !inComment && col < nCols ) {
result[(row * nCols) + col] = bonus;
++col;
/* Let's just allow anything to follow the 15 letters we
care about, e.g. comments */
if ( col >= nCols ) {
inComment = true;
}
}
}
if ( col > 0 && row < nCols - 1) {
++row;
}
}
fclose( file );
}
return result;
}
static XWBonusType
linux_util_getSquareBonus( XW_UtilCtxt* uc, XP_U16 nCols,
XP_U16 col, XP_U16 row )
{
static XWBonusType* parsedFile = NULL;
XWBonusType result = EM;
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
if ( NULL == parsedFile && NULL != cGlobals->params->bonusFile ) {
if ( !parsedFile ) {
parsedFile = parseBonusFile( nCols, cGlobals->params->bonusFile );
}
}
if ( NULL != parsedFile ) {
result = parsedFile[(row*nCols) + col];
} else {
XP_U16 nEntries;
XWBonusType* scrabbleBoard = bonusesFor( 15, &nEntries );
XP_U16 index, ii;
if ( col > (nCols/2) ) col = nCols - 1 - col;
if ( row > (nCols/2) ) row = nCols - 1 - row;
if ( col > row ) {
XP_U16 tmp = col;
col = row;
row = tmp;
}
index = col;
for ( ii = 1; ii <= row; ++ii ) {
index += ii;
}
if ( index < nEntries) {
result = scrabbleBoard[index];
}
}
return result;
} /* linux_util_getSquareBonus */
static XP_U32
linux_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) )
{
return (XP_U32)time(NULL);//tv.tv_sec;
} /* gtk_util_getCurSeconds */
static const XP_UCHAR*
linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code )
{
switch( code ) {
case STRD_REMAINING_TILES_ADD:
return (XP_UCHAR*)"+ %d [all remaining tiles]";
case STRD_UNUSED_TILES_SUB:
return (XP_UCHAR*)"- %d [unused tiles]";
case STR_COMMIT_CONFIRM:
return (XP_UCHAR*)"Are you sure you want to commit the current move?\n";
case STRD_TURN_SCORE:
return (XP_UCHAR*)"Score for turn: %d\n";
case STR_BONUS_ALL:
return (XP_UCHAR*)"Bonus for using all tiles: 50\n";
case STR_LOCAL_NAME:
return (XP_UCHAR*)"%s";
case STR_NONLOCAL_NAME:
return (XP_UCHAR*)"%s (remote)";
case STRD_TIME_PENALTY_SUB:
return (XP_UCHAR*)" - %d [time]";
/* added.... */
case STRD_CUMULATIVE_SCORE:
return (XP_UCHAR*)"Cumulative score: %d\n";
case STRS_TRAY_AT_START:
return (XP_UCHAR*)"Tray at start: %s\n";
case STRS_MOVE_DOWN:
return (XP_UCHAR*)"move (from %s down)\n";
case STRS_MOVE_ACROSS:
return (XP_UCHAR*)"move (from %s across)\n";
case STRS_NEW_TILES:
return (XP_UCHAR*)"New tiles: %s\n";
case STRSS_TRADED_FOR:
return (XP_UCHAR*)"Traded %s for %s.";
case STR_PASS:
return (XP_UCHAR*)"pass\n";
case STR_PHONY_REJECTED:
return (XP_UCHAR*)"Illegal word in move; turn lost!\n";
case STRD_ROBOT_TRADED:
return (XP_UCHAR*)"%d tiles traded this turn.";
case STR_ROBOT_MOVED:
return (XP_UCHAR*)"The robot \"%s\" moved:\n";
case STRS_REMOTE_MOVED:
return (XP_UCHAR*)"Remote player \"%s\" moved:\n";
#ifndef XWFEATURE_STANDALONE_ONLY
case STR_LOCALPLAYERS:
return (XP_UCHAR*)"Local players";
case STR_REMOTE:
return (XP_UCHAR*)"Remote";
#endif
case STR_TOTALPLAYERS:
return (XP_UCHAR*)"Total players";
case STRS_VALUES_HEADER:
return (XP_UCHAR*)"%s counts/values:\n";
case STRD_REMAINS_HEADER:
return (XP_UCHAR*)"%d tiles left in pool.";
case STRD_REMAINS_EXPL:
return (XP_UCHAR*)"%d tiles left in pool and all tray[s]:\n";
case STRSD_RESIGNED:
return "[Resigned] %s: %d";
case STRSD_WINNER:
return "[Winner] %s: %d";
case STRDSD_PLACER:
return "[#%d] %s: %d";
default:
return (XP_UCHAR*)"unknown code to linux_util_getUserString";
}
} /* linux_util_getUserString */
static const XP_UCHAR*
linux_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 code,
XP_U16 XP_UNUSED(quantity) )
{
return linux_util_getUserString( uc, code );
}
#ifdef XWFEATURE_DEVID
static const XP_UCHAR*
linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ )
{
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
return linux_getDevID( cGlobals->params, typ );
}
static void
linux_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ,
const XP_UCHAR* idRelay )
{
/* Script discon_ok2.sh is grepping for these strings in logs, so don't
change them! */
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
switch( typ ) {
case ID_TYPE_NONE: /* error case */
XP_LOGF( "%s: id rejected", __func__ );
cGlobals->params->lDevID = NULL;
break;
case ID_TYPE_RELAY:
if ( !!cGlobals->pDb && 0 < strlen( idRelay ) ) {
XP_LOGF( "%s: new id: %s", __func__, idRelay );
db_store( cGlobals->pDb, KEY_RDEVID, idRelay );
}
break;
default:
XP_ASSERT(0);
break;
}
}
#endif
#ifdef COMMS_CHECKSUM
static XP_UCHAR*
linux_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len )
{
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, len );
XP_U16 sumlen = 1 + strlen( sum );
XP_UCHAR* result = XP_MALLOC( uc->mpool, sumlen );
XP_MEMCPY( result, sum, sumlen );
g_free( sum );
return result;
}
#endif
#ifdef XWFEATURE_SMS
static XP_Bool
linux_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1,
const XP_UCHAR* p2 )
{
XP_USE( uc );
XP_Bool result = 0 == strcmp( p1, p2 );
XP_LOGF( "%s(%s, %s) => %d", __func__, p1, p2, result );
return result;
}
#endif
void
linux_util_vt_init( MPFORMAL XW_UtilCtxt* util )
{
#ifdef MEM_DEBUG
util->mpool = mpool;
#endif
util->vtable = XP_MALLOC( mpool, sizeof(UtilVtable) );
util->vtable->m_util_makeEmptyDict = linux_util_makeEmptyDict;
util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus;
util->vtable->m_util_getCurSeconds = linux_util_getCurSeconds;
util->vtable->m_util_getUserString = linux_util_getUserString;
util->vtable->m_util_getUserQuantityString = linux_util_getUserQuantityString;
#ifdef XWFEATURE_DEVID
util->vtable->m_util_getDevID = linux_util_getDevID;
util->vtable->m_util_deviceRegistered = linux_util_deviceRegistered;
#endif
#ifdef COMMS_CHECKSUM
util->vtable->m_util_md5sum = linux_util_md5sum;
#endif
#ifdef XWFEATURE_SMS
util->vtable->m_util_phoneNumbersSame = linux_util_phoneNumbersSame;
#endif
}
void
linux_util_vt_destroy( XW_UtilCtxt* util )
{
XP_FREE( util->mpool, util->vtable );
}
const XP_UCHAR*
linux_getErrString( UtilErrID id, XP_Bool* silent )
{
*silent = XP_FALSE;
const char* message = NULL;
switch( (int)id ) {
case ERR_TILES_NOT_IN_LINE:
message = "All tiles played must be in a line.";
break;
case ERR_NO_EMPTIES_IN_TURN:
message = "Empty squares cannot separate tiles played.";
break;
case ERR_TOO_FEW_TILES_LEFT_TO_TRADE:
message = "Too few tiles left to trade.";
break;
case ERR_TWO_TILES_FIRST_MOVE:
message = "Must play two or more pieces on the first move.";
break;
case ERR_TILES_MUST_CONTACT:
message = "New pieces must contact others already in place (or "
"the middle square on the first move).";
break;
case ERR_NOT_YOUR_TURN:
message = "You can't do that; it's not your turn!";
break;
case ERR_NO_PEEK_ROBOT_TILES:
message = "No peeking at the robot's tiles!";
break;
#ifndef XWFEATURE_STANDALONE_ONLY
case ERR_NO_PEEK_REMOTE_TILES:
message = "No peeking at remote players' tiles!";
break;
case ERR_REG_UNEXPECTED_USER:
message = "Refused attempt to register unexpected user[s].";
break;
case ERR_SERVER_DICT_WINS:
message = "Conflict between Host and Guest dictionaries; Host wins.";
XP_WARNF( "GTK may have problems here." );
break;
case ERR_REG_SERVER_SANS_REMOTE:
message = "At least one player must be marked remote for a game "
"started as Host.";
break;
#endif
case ERR_NO_EMPTY_TRADE:
message = "No tiles selected; trade cancelled.";
break;
case ERR_NO_HINT_FOUND:
message = "Unable to suggest any moves.";
break;
case ERR_CANT_UNDO_TILEASSIGN:
message = "Tile assignment can't be undone.";
break;
case ERR_CANT_HINT_WHILE_DISABLED:
message = "The hint feature is disabled for this game. Enable "
"it for a new game using the Preferences dialog.";
break;
/* case INFO_REMOTE_CONNECTED: */
/* message = "Another device has joined the game"; */
/* break; */
case ERR_RELAY_BASE + XWRELAY_ERROR_LOST_OTHER:
*silent = XP_TRUE;
message = "XWRELAY_ERROR_LOST_OTHER";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_TIMEOUT:
message = "The relay timed you out; other players "
"have left or never showed up.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_YOU:
message = "You were disconnected from relay because it didn't "
"hear from you in too long.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_OTHER:
/* *silent = XP_TRUE; */
message = "The relay has lost contact with a device in this game.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_OLDFLAGS:
message = "You need to upgrade your copy of Crosswords.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_SHUTDOWN:
message = "Relay disconnected you to shut down (and probably reboot).";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_BADPROTO:
message = "XWRELAY_ERROR_BADPROTO";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_RELAYBUSY:
message = "XWRELAY_ERROR_RELAYBUSY";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_OTHER_DISCON:
*silent = XP_TRUE; /* happens all the time, and shouldn't matter */
message = "XWRELAY_ERROR_OTHER_DISCON";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_NO_ROOM:
message = "No such room. Has the host connected yet to reserve it?";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_DUP_ROOM:
message = "That room is reserved by another host. Rename your room, "
"become a guest, or try again in a few minutes.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_TOO_MANY:
message = "You tried to supply more players than the host expected.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_DELETED:
message = "Game deleted .";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_NORECONN:
message = "Cannot reconnect.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_DEADGAME:
message = "Game is listed as dead on relay.";
break;
default:
XP_LOGF( "no code for error: %d", id );
message = "<unrecognized error code reported>";
}
return (XP_UCHAR*)message;
} /* linux_getErrString */
void
formatLMI( const LastMoveInfo* lmi, XP_UCHAR* buf, XP_U16 len )
{
const XP_UCHAR* name = lmi->name;
switch( lmi->moveType ) {
case ASSIGN_TYPE:
XP_SNPRINTF( buf, len, "Tiles assigned to %s", name );
break;
case MOVE_TYPE:
if ( 0 == lmi->nTiles ) {
XP_SNPRINTF( buf, len, "%s passed", name );
} else {
XP_SNPRINTF( buf, len, "%s played %s for %d points", name,
lmi->word, lmi->score );
}
break;
case TRADE_TYPE:
XP_SNPRINTF( buf, len, "%s traded %d tiles",
name, lmi->nTiles );
break;
case PHONY_TYPE:
XP_SNPRINTF( buf, len, "%s lost a turn", name );
break;
default:
XP_ASSERT(0);
break;
}
}
void
formatConfirmTrade( const XP_UCHAR** tiles, XP_U16 nTiles,
char* buf, XP_U16 buflen )
{
char tileBuf[128];
int offset = 0;
int ii;
XP_ASSERT( nTiles > 0 );
for ( ii = 0; ii < nTiles; ++ii ) {
offset += snprintf( &tileBuf[offset], sizeof(tileBuf) - offset,
"%s, ", tiles[ii] );
XP_ASSERT( offset < sizeof(tileBuf) );
}
tileBuf[offset-2] = '\0';
snprintf( buf, buflen,
"Are you sure you want to trade the selected tiles (%s)?",
tileBuf );
}
typedef struct _MsgRec {
XP_U8* msg;
XP_U16 msglen;
} MsgRec;
void
initNoConnStorage( CommonGlobals* cGlobals )
{
XP_ASSERT( NULL == cGlobals->noConnMsgs );
cGlobals->noConnMsgs = g_hash_table_new( g_str_hash, g_str_equal );
}
XP_Bool
storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 msglen,
const XP_UCHAR* relayID )
{
XP_ASSERT( 0 < msglen );
XP_Bool inUse = NULL != cGlobals->noConnMsgs;
if ( inUse ) {
GSList* list = g_hash_table_lookup( cGlobals->noConnMsgs, relayID );
gboolean missing = NULL == list;
MsgRec* msgrec = g_malloc( sizeof(*msgrec) );
msgrec->msg = g_malloc( msglen );
XP_MEMCPY( msgrec->msg, msg, msglen );
msgrec->msglen = msglen;
list = g_slist_append( list, msgrec );
if ( missing ) {
gchar* key = g_strdup( relayID ); /* will leak */
g_hash_table_insert( cGlobals->noConnMsgs, key, list );
}
}
return inUse;
}
XWStreamCtxt*
make_simple_stream( CommonGlobals* cGlobals )
{
XWStreamCtxt* stream =
mem_stream_make( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr,
cGlobals, CHANNEL_NONE, NULL );
return stream;
}
void
writeNoConnMsgs( CommonGlobals* cGlobals, int fd )
{
GHashTable* hash = cGlobals->noConnMsgs;
GList* keys = g_hash_table_get_keys( hash );
GList* iter;
for ( iter = keys; !!iter; iter = iter->next ) {
XP_UCHAR* relayID = (XP_UCHAR*)iter->data;
GSList* list = (GSList*)g_hash_table_lookup( hash, relayID );
guint nMsgs = g_slist_length( list );
XP_ASSERT( 0 < nMsgs );
XWStreamCtxt* stream = make_simple_stream( cGlobals );
stream_putU16( stream, 1 ); /* number of relayIDs */
stream_catString( stream, relayID );
stream_putU8( stream, '\n' );
stream_putU16( stream, nMsgs );
int ii;
for ( ii = 0; ii < nMsgs; ++ii ) {
MsgRec* rec = g_slist_nth_data( list, ii );
stream_putU16( stream, rec->msglen );
stream_putBytes( stream, rec->msg, rec->msglen );
g_free( rec->msg );
g_free( rec );
}
g_slist_free( list );
XP_U16 siz = stream_getSize( stream );
/* XP_U8 buf[siz]; */
/* stream_getBytes( stream, buf, siz ); */
XP_U16 tmp = XP_HTONS( siz );
#ifdef DEBUG
ssize_t nwritten =
#endif
write( fd, &tmp, sizeof(tmp) );
XP_ASSERT( nwritten == sizeof(tmp) );
#ifdef DEBUG
nwritten =
#endif
write( fd, stream_getPtr( stream ), siz );
XP_ASSERT( nwritten == siz );
stream_destroy( stream );
}
g_list_free( keys );
g_hash_table_unref( hash );
cGlobals->noConnMsgs = NULL;
} /* writeNoConnMsgs */
#ifdef TEXT_MODEL
/* This is broken for UTF-8, even Spanish */
void
linux_lowerstr( XP_UCHAR* str )
{
while ( *str ) {
*str = tolower( *str );
++str;
}
}
#endif