xwords/xwords4/android/anddict.c
ehouse 68079d3590 Add ability to open utf8-formatted dicts and to display bitmap tiles
(though poorly.)  Both required processing in the jni that isn't
possible (e.g. because no libiconv included) so I created callbacks
into java from the dict building code.  Add ability to download dicts,
to select them, to add and remove players and make them robots or
human.  Robot-vs-robot game doesn't work well (robots trade a lot and
server_do seems not to be getting called enough) but will soon.
Coalesce penMove events.  Implement game list menuitems like delete,
copy, etc.
2010-01-25 02:49:14 +00:00

409 lines
12 KiB
C

/* -*-mode: C; compile-command: "cd XWords4; ../scripts/ndkbuild.sh"; -*- */
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include "anddict.h"
#include "xptypes.h"
#include "dictnry.h"
#include "strutils.h"
#include "utilwrapper.h"
typedef struct _AndDictionaryCtxt {
DictionaryCtxt super;
XW_UtilCtxt* util;
JNIEnv *env;
XP_U8* bytes;
} AndDictionaryCtxt;
void
dict_splitFaces( DictionaryCtxt* dict, const XP_U8* bytes,
XP_U16 nBytes, XP_U16 nFaces )
{
LOG_FUNC();
XP_UCHAR* faces = (XP_UCHAR*)XP_CALLOC( dict->mpool, nBytes + nFaces );
XP_UCHAR** ptrs = (XP_UCHAR**)XP_CALLOC( dict->mpool, nFaces * sizeof(ptrs[0]));
XP_U16 ii;
XP_UCHAR* next = faces;
/* now split; this will not work for utf8!!! */
for ( ii = 0; ii < nFaces; ++ii ) {
ptrs[ii] = next;
*next++ = *bytes++;
*next++ = 0;
}
XP_ASSERT( next == faces + nFaces + nBytes );
XP_ASSERT( !dict->faces );
dict->faces = faces;
XP_ASSERT( !dict->facePtrs );
dict->facePtrs = ptrs;
}
static XP_U32
n_ptr_tohl( XP_U8** inp )
{
XP_U32 t;
XP_MEMCPY( &t, *inp, sizeof(t) );
*inp += sizeof(t);
return XP_NTOHL(t);
} /* n_ptr_tohl */
static XP_U16
n_ptr_tohs( XP_U8** inp )
{
XP_U16 t;
XP_MEMCPY( &t, *inp, sizeof(t) );
*inp += sizeof(t);
return XP_NTOHS(t);
} /* n_ptr_tohs */
static XP_U16
andCountSpecials( AndDictionaryCtxt* ctxt )
{
XP_U16 result = 0;
XP_U16 ii;
for ( ii = 0; ii < ctxt->super.nFaces; ++ii ) {
if ( IS_SPECIAL( ctxt->super.facePtrs[ii][0] ) ) {
++result;
}
}
return result;
} /* andCountSpecials */
static XP_Bitmap
andMakeBitmap( AndDictionaryCtxt* ctxt, XP_U8** ptrp )
{
XP_U8* ptr = *ptrp;
XP_U8 nCols = *ptr++;
jobject bitmap = NULL;
if ( nCols > 0 ) {
XP_U8* savedDest;
XP_U8 nRows = *ptr++;
XP_U16 rowBytes = (nCols+7) / 8;
XP_U8 srcByte = 0;
XP_U8 nBits;
XP_U16 ii;
jboolean* colors = (jboolean*)XP_CALLOC( ctxt->super.mpool,
nCols * nRows * sizeof(*colors) );
jboolean* next = colors;
nBits = nRows * nCols;
for ( ii = 0; ii < nBits; ++ii ) {
XP_U8 srcBitIndex = ii % 8;
XP_U8 destBitIndex = (ii % nCols) % 8;
XP_U8 srcMask, bit;
if ( srcBitIndex == 0 ) {
srcByte = *ptr++;
}
srcMask = 1 << (7 - srcBitIndex);
XP_ASSERT( next < (colors + (nRows * nCols)) );
*next++ = ((srcByte & srcMask) == 0) ? JNI_FALSE : JNI_TRUE;
}
JNIEnv* env = ctxt->env;
bitmap = and_util_makeJBitmap( ctxt->util, nCols, nRows, colors );
jobject tmp = (*env)->NewGlobalRef( env, bitmap );
XP_ASSERT( tmp == bitmap );
(*env)->DeleteLocalRef( env, bitmap );
XP_FREE( ctxt->super.mpool, colors );
}
*ptrp = ptr;
return (XP_Bitmap)bitmap;
} /* andMakeBitmap */
static void
andLoadSpecialData( AndDictionaryCtxt* ctxt, XP_U8** ptrp )
{
XP_U16 nSpecials = andCountSpecials( ctxt );
XP_U8* ptr = *ptrp;
Tile ii;
XP_UCHAR** texts;
SpecialBitmaps* bitmaps;
texts = (XP_UCHAR**)XP_MALLOC( ctxt->super.mpool,
nSpecials * sizeof(*texts) );
bitmaps = (SpecialBitmaps*)
XP_CALLOC( ctxt->super.mpool, nSpecials * sizeof(*bitmaps) );
for ( ii = 0; ii < ctxt->super.nFaces; ++ii ) {
const XP_UCHAR* facep = ctxt->super.facePtrs[(short)ii];
if ( IS_SPECIAL(*facep) ) {
/* get the string */
XP_U8 txtlen = *ptr++;
XP_UCHAR* text = (XP_UCHAR*)XP_MALLOC(ctxt->super.mpool, txtlen+1);
XP_MEMCPY( text, ptr, txtlen );
ptr += txtlen;
text[txtlen] = '\0';
XP_ASSERT( *facep < nSpecials ); /* firing */
texts[(int)*facep] = text;
bitmaps[(int)*facep].largeBM = andMakeBitmap( ctxt, &ptr );
bitmaps[(int)*facep].smallBM = andMakeBitmap( ctxt, &ptr );
}
}
ctxt->super.chars = texts;
ctxt->super.bitmaps = bitmaps;
*ptrp = ptr;
} /* andLoadSpecialData */
/** Android doesn't include iconv for C code to use, so we'll have java do it.
* Cons up a string with all the tile faces (skipping the specials to make
* things easier) and have java return an array of strings. Then load one at
* a time into the expected null-separated format.
*/
static void
splitFaces_via_java( JNIEnv* env, AndDictionaryCtxt* ctxt, const XP_U8* ptr,
int nFaceBytes, int nFaces )
{
XP_UCHAR facesBuf[nFaces*4]; /* seems a reasonable upper bound... */
int indx = 0;
int offsets[nFaces];
int nBytes;
int ii;
jobject jstrarr = and_util_splitFaces( ctxt->util, ptr, nFaceBytes );
XP_ASSERT( (*env)->GetArrayLength( env, jstrarr ) == nFaces );
for ( ii = 0; ii < nFaces; ++ii ) {
jobject jstr = (*env)->GetObjectArrayElement( env, jstrarr, ii );
offsets[ii] = indx;
nBytes = (*env)->GetStringUTFLength( env, jstr );
const char* bytes = (*env)->GetStringUTFChars( env, jstr, NULL );
char* end;
long numval = strtol( bytes, &end, 10 );
if ( end > bytes ) {
XP_ASSERT( numval < 32 );
nBytes = 1;
facesBuf[indx] = (XP_UCHAR)numval;
} else {
XP_MEMCPY( &facesBuf[indx], bytes, nBytes );
}
(*env)->ReleaseStringUTFChars( env, jstr, bytes );
(*env)->DeleteLocalRef( env, jstr );
indx += nBytes;
facesBuf[indx++] = '\0';
XP_ASSERT( indx < VSIZE(facesBuf) );
}
(*env)->DeleteLocalRef( env, jstrarr );
XP_UCHAR* faces = (XP_UCHAR*)XP_CALLOC( ctxt->super.mpool, indx );
XP_UCHAR** ptrs = (XP_UCHAR**)XP_CALLOC( ctxt->super.mpool,
nFaces * sizeof(ptrs[0]));
XP_MEMCPY( faces, facesBuf, indx );
for ( ii = 0; ii < nFaces; ++ii ) {
ptrs[ii] = &faces[offsets[ii]];
}
XP_ASSERT( !ctxt->super.faces );
ctxt->super.faces = faces;
XP_ASSERT( !ctxt->super.facePtrs );
ctxt->super.facePtrs = ptrs;
} /* splitFaces_via_java */
static void
parseDict( AndDictionaryCtxt* ctxt, XP_U8* ptr, XP_U32 dictLength )
{
while( !!ptr ) { /* lets us break.... */
XP_U32 offset;
XP_U16 nFaces, numFaceBytes = 0;
XP_U16 i;
XP_U16 flags;
void* mappedBase = (void*)ptr;
XP_U8 nodeSize;
XP_Bool isUTF8 = XP_FALSE;
flags = n_ptr_tohs( &ptr );
if ( flags == 0x0002 ) {
nodeSize = 3;
} else if ( flags == 0x0003 ) {
nodeSize = 4;
} else if ( flags == 0x0004 ) {
isUTF8 = XP_TRUE;
nodeSize = 3;
} else if ( flags == 0x0005 ) {
isUTF8 = XP_TRUE;
nodeSize = 4;
} else {
break; /* we want to return NULL */
}
if ( isUTF8 ) {
numFaceBytes = (XP_U16)(*ptr++);
}
nFaces = (XP_U16)(*ptr++);
if ( nFaces > 64 ) {
break;
}
ctxt->super.nodeSize = nodeSize;
if ( !isUTF8 ) {
numFaceBytes = nFaces * 2;
}
ctxt->super.nFaces = (XP_U8)nFaces;
ctxt->super.isUTF8 = isUTF8;
if ( isUTF8 ) {
splitFaces_via_java( ctxt->env, ctxt, ptr, numFaceBytes, nFaces );
ptr += numFaceBytes;
} else {
XP_U8 tmp[nFaces*4]; /* should be enough... */
XP_U16 nBytes = 0;
XP_U16 ii;
/* Need to translate from iso-8859-n to utf8 */
for ( ii = 0; ii < nFaces; ++ii ) {
XP_UCHAR ch = ptr[1];
ptr += 2;
tmp[nBytes] = ch;
nBytes += 1;
}
dict_splitFaces( &ctxt->super, tmp, nBytes, nFaces );
}
ctxt->super.is_4_byte = (ctxt->super.nodeSize == 4);
ctxt->super.countsAndValues =
(XP_U8*)XP_MALLOC(ctxt->super.mpool, nFaces*2);
ptr += 2; /* skip xloc header */
for ( i = 0; i < nFaces*2; i += 2 ) {
ctxt->super.countsAndValues[i] = *ptr++;
ctxt->super.countsAndValues[i+1] = *ptr++;
}
andLoadSpecialData( ctxt, &ptr );
dictLength -= ptr - (XP_U8*)mappedBase;
if ( dictLength > sizeof(XP_U32) ) {
offset = n_ptr_tohl( &ptr );
dictLength -= sizeof(offset);
#ifdef NODE_CAN_4
XP_ASSERT( dictLength % ctxt->super.nodeSize == 0 );
# ifdef DEBUG
ctxt->super.numEdges = dictLength / ctxt->super.nodeSize;
# endif
#else
XP_ASSERT( dictLength % 3 == 0 );
# ifdef DEBUG
ctxt->super.numEdges = dictLength / 3;
# endif
#endif
} else {
offset = 0;
}
if ( dictLength > 0 ) {
ctxt->super.base = (array_edge*)ptr;
#ifdef NODE_CAN_4
ctxt->super.topEdge = ctxt->super.base
+ (offset * ctxt->super.nodeSize);
#else
ctxt->super.topEdge = ctxt->super.base + (offset * 3);
#endif
} else {
ctxt->super.topEdge = (array_edge*)NULL;
ctxt->super.base = (array_edge*)NULL;
}
setBlankTile( &ctxt->super );
ctxt->super.name = copyString(ctxt->super.mpool, "no name dict" );
break; /* exit phony while loop */
}
}
static void
and_dictionary_destroy( DictionaryCtxt* dict )
{
LOG_FUNC();
AndDictionaryCtxt* ctxt = (AndDictionaryCtxt*)dict;
XP_U16 nSpecials = andCountSpecials( ctxt );
XP_U16 ii;
if ( !!ctxt->super.chars ) {
for ( ii = 0; ii < nSpecials; ++ii ) {
XP_UCHAR* text = ctxt->super.chars[ii];
if ( !!text ) {
XP_FREE( ctxt->super.mpool, text );
}
}
XP_FREE( ctxt->super.mpool, ctxt->super.chars );
}
if ( !!ctxt->super.bitmaps ) {
JNIEnv* env = ctxt->env;
for ( ii = 0; ii < nSpecials; ++ii ) {
jobject bitmap = ctxt->super.bitmaps[ii].largeBM;
if ( !!bitmap ) {
(*env)->DeleteGlobalRef( env, bitmap );
}
}
XP_FREE( ctxt->super.mpool, ctxt->super.bitmaps );
}
XP_FREE( ctxt->super.mpool, ctxt->super.faces );
XP_FREE( ctxt->super.mpool, ctxt->super.facePtrs );
XP_FREE( ctxt->super.mpool, ctxt->super.countsAndValues );
XP_FREE( ctxt->super.mpool, ctxt->super.name );
XP_FREE( ctxt->super.mpool, ctxt->bytes );
XP_FREE( ctxt->super.mpool, ctxt );
LOG_RETURN_VOID();
}
DictionaryCtxt*
makeDict( MPFORMAL JNIEnv *env, XW_UtilCtxt* util, jbyteArray jbytes )
{
XP_Bool formatOk = XP_TRUE;
XP_Bool isUTF8 = XP_FALSE;
XP_U16 charSize;
AndDictionaryCtxt* anddict = NULL;
jsize len = (*env)->GetArrayLength( env, jbytes );
XP_LOGF( "%s: got %d bytes", __func__, len );
XP_U8* localBytes = XP_MALLOC( mpool, len );
jbyte* src = (*env)->GetByteArrayElements( env, jbytes, NULL );
XP_MEMCPY( localBytes, src, len );
(*env)->ReleaseByteArrayElements( env, jbytes, src, 0 );
anddict = (AndDictionaryCtxt*)XP_CALLOC( mpool, sizeof( *anddict ) );
dict_super_init( (DictionaryCtxt*)anddict );
anddict->super.destructor = and_dictionary_destroy;
/* anddict->super.func_dict_getShortName = and_dict_getShortName; */
MPASSIGN(anddict->super.mpool, mpool);
anddict->bytes = localBytes;
anddict->env = env;
anddict->util = util;
parseDict( anddict, localBytes, len );
setBlankTile( &anddict->super );
err:
return (DictionaryCtxt*)anddict;
}