xwords/xwords4/wasm/main.c

2128 lines
62 KiB
C
Raw Normal View History

/* -*- compile-command: "cd ../wasm && make MEMDEBUG=TRUE install -j3"; -*- */
2021-02-06 17:54:08 +01:00
/*
* Copyright 2021 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.
*/
2021-02-01 17:30:34 +01:00
2021-02-26 05:39:08 +01:00
/* TODO
*
* Downloading wordlists. Will likely need to fetch and store them as base64
* data, meaning I'll need a working btoa to use them. Here's one way to download:
* https://emscripten.org/docs/api_reference/fetch.html
*/
2021-02-02 22:49:27 +01:00
#include <sys/time.h>
2021-02-01 17:30:34 +01:00
#include <stdio.h>
2021-02-02 05:13:25 +01:00
#include <stdarg.h>
2021-02-01 17:30:34 +01:00
#include <SDL2/SDL.h>
2021-02-02 18:08:41 +01:00
#include <SDL2/SDL_ttf.h>
2021-02-01 17:30:34 +01:00
#include <emscripten.h>
#include <unistd.h>
#include <stdlib.h>
2021-02-02 05:13:25 +01:00
#include "game.h"
2021-02-04 01:19:16 +01:00
#include "device.h"
2021-02-02 05:13:25 +01:00
#include "mempool.h"
2021-02-07 21:48:06 +01:00
#include "nli.h"
#include "strutils.h"
2021-02-10 23:16:22 +01:00
#include "movestak.h"
2021-02-24 01:00:19 +01:00
#include "knownplyr.h"
2021-02-02 05:13:25 +01:00
#include "main.h"
2021-02-02 05:13:25 +01:00
#include "wasmdraw.h"
#include "wasmutil.h"
#include "wasmdutil.h"
#include "wasmdict.h"
2021-02-01 17:30:34 +01:00
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
2021-02-02 05:13:25 +01:00
#define WASM_BOARD_LEFT 0
#define WASM_HOR_SCORE_TOP 0
2021-03-10 03:17:45 +01:00
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 1040
2021-02-07 02:36:53 +01:00
#define BDWIDTH WINDOW_WIDTH
#define BDHEIGHT WINDOW_HEIGHT
2021-02-01 17:30:34 +01:00
#define KEY_LAST_GID "cur_game"
2021-02-17 00:32:33 +01:00
#define KEY_PLAYER_NAME "player_name"
2021-03-05 18:03:32 +01:00
#define KEY_GAMES "games"
#define KEY_SUMMARY "summary"
#define KEY_GAME "game_data"
#define KEY_NAME "game_name"
#define KEY_NEXT_GAME "next_game"
2021-03-08 16:39:47 +01:00
#define KEY_LANG_NAME "lang_name"
2021-02-19 20:19:16 +01:00
#define BUTTON_OK "OK"
#define BUTTON_CANCEL "Cancel"
2021-02-19 04:20:20 +01:00
#define BUTTONS_ID_GAME "game_buttons"
#define BUTTONS_ID_DEVICE "device_buttons"
#define BUTTON_HINTDOWN "Prev Hint"
#define BUTTON_HINTUP "Next Hint"
#define BUTTON_TRADE "Trade"
#define BUTTON_STOPTRADE "Cancel Trade"
#define BUTTON_COMMIT "Commit"
#define BUTTON_FLIP "Flip"
#define BUTTON_REDO "Redo"
#define BUTTON_VALS "Vals"
#define BUTTON_INVITE "Invite"
2021-02-19 04:20:20 +01:00
2021-03-09 20:01:38 +01:00
#define BUTTON_GAME_GAMES "Games"
2021-02-19 04:20:20 +01:00
#define BUTTON_GAME_NEW "New Game"
#define BUTTON_GAME_RENAME "Rename Game"
#define BUTTON_GAME_DELETE "Delete Game"
#define BUTTON_NAME "My Name"
2021-02-19 20:19:16 +01:00
#define MAX_BUTTONS 20 /* not sure what's safe here */
// I get a JS exception if I do this... So don't
// #define GLOBALS_ON_STACK
typedef struct _NewGameParams {
2021-03-10 05:13:42 +01:00
Globals* globals;
bool isRobotNotRemote;
bool hintsNotAllowed;
2021-03-10 05:13:42 +01:00
char lc[8];
} NewGameParams;
2021-02-19 04:20:20 +01:00
static void updateScreen( GameState* gs, bool doSave );
2021-02-27 00:45:52 +01:00
static void updateDeviceButtons( Globals* globals );
static void clearScreen( Globals* globals );
static GameState* newGameState( Globals* globals );
static GameState* getSavedGame( Globals* globals, int gameID );
static void loadAndDraw( Globals* globals, const NetLaunchInfo* invite,
int gameID, NewGameParams* params );
static GameState* getCurGame( Globals* globals );
static void nameGame( GameState* gs, const char* name );
static void ensureName( GameState* gs );
static void loadName( GameState* gs );
static void saveName( GameState* gs );
2021-02-24 01:00:19 +01:00
static bool isVisible( GameState* gs );
static int countDicts( Globals* globals );
2021-02-19 04:20:20 +01:00
typedef struct _GotDictData {
const char* lc;
const char* langName;
const char* dictName;
uint8_t* data;
int len;
} GotDictData;
typedef void (*GotDictProc)(void* closure, GotDictData* data);
EM_JS(void, call_get_dict, (const char* lc, GotDictProc proc,
void* closure), {
let langs;
if (lc) {
langs = [UTF8ToString(lc)];
} else {
langs = [navigator.language.split('-')[0]];
if (langs[0] != 'en') {
langs.push('en');
}
}
getDict(langs, proc, closure);
});
EM_JS(void, show_name, (const char* name), {
2021-03-08 16:54:05 +01:00
let jsname = name ? UTF8ToString(name) : "";
document.getElementById('gamename').textContent = jsname;
});
2021-03-05 18:46:21 +01:00
EM_JS(void, show_pool, (int cur, int max), {
const msg = 'cur: ' + cur + 'b; max: ' + max + 'b';
const div = document.getElementById('mempool');
div.textContent = msg;
div.parentNode.hidden = 0;
2021-03-05 18:46:21 +01:00
});
EM_JS(void, call_dialog, (const char* str, const char** but_strs,
2021-02-21 19:07:51 +01:00
StringProc proc, void* closure), {
var buttons = [];
2021-02-18 03:58:46 +01:00
for ( let ii = 0; ; ++ii ) {
const mem = HEAP32[(but_strs + (ii * 4)) >> 2];
if ( 0 == mem ) {
break;
}
const str = UTF8ToString(mem);
buttons.push(str);
}
nbDialog(UTF8ToString(str), buttons, proc, closure);
} );
2021-02-18 03:58:46 +01:00
EM_JS(void, call_pickBlank, (const char* msg, const char** strs, int nStrs,
2021-02-21 19:07:51 +01:00
StringProc proc, void* closure), {
2021-02-18 03:58:46 +01:00
var buttons = [];
for ( let ii = 0; ii < nStrs; ++ii ) {
const mem = HEAP32[(strs + (ii * 4)) >> 2];
const str = UTF8ToString(mem);
buttons.push(str);
}
nbBlankPick(UTF8ToString(msg), buttons, proc, closure);
} );
EM_JS(void, call_pickGame, (const char* msg, char** ids, char** names,
int nEntries, StringProc proc, void* closure), {
let map = {};
for (let ii = 0; ii < nEntries; ++ii ) {
const idsMem = HEAP32[(ids + (ii * 4)) >> 2];
const namesMem = HEAP32[(names + (ii * 4)) >> 2];
let id = UTF8ToString(idsMem);
map[id] = UTF8ToString(namesMem);
}
2021-02-19 20:19:16 +01:00
nbGamePick(UTF8ToString(msg), map, proc, closure);
} );
2021-02-18 03:58:46 +01:00
2021-02-16 23:25:22 +01:00
EM_JS(void, call_get_string, (const char* msg, const char* dflt,
2021-02-21 19:07:51 +01:00
StringProc proc, void* closure), {
2021-02-16 23:25:22 +01:00
let jsMgs = UTF8ToString(msg);
let jsDflt = UTF8ToString(dflt);
nbGetString( jsMgs, jsDflt, proc, closure );
} );
2021-03-09 20:39:43 +01:00
EM_JS(void, call_setup, (void* closure, bool dbg, const char* devid,
const char* gitrev, int now,
StringProc conflictProc,
StringProc focussedProc,
BinProc msgProc), {
2021-02-22 06:59:03 +01:00
let jsgr = UTF8ToString(gitrev);
2021-03-09 20:39:43 +01:00
jssetup(closure, dbg, UTF8ToString(devid), jsgr, now,
conflictProc, focussedProc, msgProc);
2021-02-22 06:59:03 +01:00
});
2021-02-07 21:48:06 +01:00
2021-02-10 03:56:13 +01:00
EM_JS(bool, call_mqttSend, (const char* topic, const uint8_t* ptr, int len), {
2021-02-07 21:48:06 +01:00
let topStr = UTF8ToString(topic);
let buffer = new Uint8Array(Module.HEAPU8.buffer, ptr, len);
2021-02-10 03:56:13 +01:00
return mqttSend(topStr, buffer);
2021-02-07 21:48:06 +01:00
});
2021-02-02 23:52:25 +01:00
2021-02-10 22:21:28 +01:00
typedef void (*JSCallback)(void* closure);
EM_JS(void, jscallback_set, (JSCallback proc, void* closure, int inMS), {
let timerproc = function(closure) {
2021-02-21 19:07:51 +01:00
ccall('cbckVoid', null, ['number', 'number'], [proc, closure]);
2021-02-10 22:21:28 +01:00
};
setTimeout( timerproc, inMS, closure );
});
2021-02-19 04:20:20 +01:00
EM_JS(void, setButtons, (const char* id, const char** bstrs,
2021-02-21 19:07:51 +01:00
StringProc proc, void* closure), {
2021-02-19 04:20:20 +01:00
var buttons = [];
for ( let ii = 0; ; ++ii ) {
const mem = HEAP32[(bstrs + (ii * 4)) >> 2];
if ( 0 == mem ) {
break;
}
const str = UTF8ToString(mem);
buttons.push(str);
}
setDivButtons(UTF8ToString(id), buttons, proc, closure);
});
EM_JS(void, js_callNewGame, (const char* msg, void* closure,
char** langs, int nLangs), {
let jsmsg = UTF8ToString(msg);
let jlangs = [];
for ( let ii = 0; ii < nLangs; ++ii ) {
const mem = HEAP32[(langs + (ii * 4)) >> 2];
let str = UTF8ToString(mem);
jlangs.push(str);
}
nbGetNewGame(closure, jsmsg, jlangs);
});
2021-03-10 05:13:42 +01:00
EM_JS(bool, js_getHaveNotifyPerm, (), {
let perm = Notification.permission;
return perm == 'granted';
});
EM_JS(void, js_requestNotify, (), {
Notification.requestPermission();
});
EM_JS( void, js_notify, (const char* msg), {
const jsmsg = UTF8ToString(msg);
new Notification('WASM CrossWords',
{ body: jsmsg, renotify: true, }
);
});
2021-02-15 23:59:44 +01:00
typedef struct _ConfirmState {
Globals* globals;
2021-03-05 21:04:07 +01:00
BoolProc proc;
2021-02-15 23:59:44 +01:00
void* closure;
} ConfirmState;
static void
onConfirmed( void* closure, const char* button )
{
bool confirmed = 0 == strcmp( button, BUTTON_OK );
ConfirmState* cs = (ConfirmState*)closure;
(*cs->proc)( cs->closure, confirmed );
XP_FREE( cs->globals->mpool, cs );
}
static void
call_confirm( Globals* globals, const char* msg,
2021-03-05 21:04:07 +01:00
BoolProc proc, void* closure )
2021-02-15 23:59:44 +01:00
{
const char* buttons[] = { BUTTON_CANCEL, BUTTON_OK, NULL };
ConfirmState* cs = XP_MALLOC( globals->mpool, sizeof(*cs) );
cs->globals = globals;
cs->proc = proc;
cs->closure = closure;
call_dialog( msg, buttons, onConfirmed, cs );
}
static void
call_alert( const char* msg )
{
const char* buttons[] = { BUTTON_OK, NULL };
call_dialog( msg, buttons, NULL, NULL );
}
static bool
sendStreamToDev( XWStreamCtxt* stream, const MQTTDevID* devID )
{
XP_S16 nSent = -1;
XP_UCHAR topic[64];
formatMQTTTopic( devID, topic, sizeof(topic) );
XP_U16 streamLen = stream_getSize( stream );
bool success = call_mqttSend( topic, stream_getPtr( stream ), streamLen );
stream_destroy( stream, NULL );
LOG_RETURNF("%d", nSent);
return success;
}
2021-02-07 21:48:06 +01:00
static XP_S16
send_msg( XWEnv xwe, const XP_U8* buf, XP_U16 len,
const XP_UCHAR* msgNo, const CommsAddrRec* addr,
CommsConnType conType, XP_U32 gameID, void* closure )
{
XP_S16 nSent = -1;
Globals* globals = (Globals*)closure;
if ( addr_hasType( addr, COMMS_CONN_MQTT ) ) {
// MQTTDevID devID = addr->u.mqtt.devID;
2021-02-07 21:48:06 +01:00
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
globals->vtMgr );
dvc_makeMQTTMessage( globals->dutil, NULL, stream,
gameID, buf, len );
if ( sendStreamToDev( stream, &addr->u.mqtt.devID ) ) {
2021-02-10 03:56:13 +01:00
nSent = len;
}
2021-02-07 21:48:06 +01:00
}
LOG_RETURNF( "%d", nSent );
return nSent;
}
2021-02-19 20:19:16 +01:00
static void
formatGameID( char* buf, size_t len, int gameID )
{
2021-03-05 18:03:32 +01:00
snprintf( buf, len, "%X", gameID );
2021-02-19 20:19:16 +01:00
}
static void
unformatGameID( int* gameID, const char* gameIDStr )
{
2021-03-05 18:03:32 +01:00
sscanf( gameIDStr, "%X", gameID );
XP_LOGFF( "unformatGameID(%s) => %X", gameIDStr, *gameID );
}
static void
makeSelfAddr( Globals* globals, CommsAddrRec* addr )
{
addr_setType( addr, COMMS_CONN_MQTT );
dvc_getMQTTDevID( globals->dutil, NULL, &addr->u.mqtt.devID );
}
2021-02-24 01:00:19 +01:00
static void
sendInviteTo(GameState* gs, const MQTTDevID* remoteDevID)
{
Globals* globals = gs->globals;
CommsAddrRec myAddr = {0};
makeSelfAddr( globals, &myAddr );
NetLaunchInfo nli = {0}; /* include everything!!! */
nli_init( &nli, &gs->gi, &myAddr, 1, 1 );
nli_setGameName( &nli, gs->gameName );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
globals->vtMgr );
dvc_makeMQTTInvite( globals->dutil, NULL, stream, &nli );
sendStreamToDev( stream, remoteDevID );
}
static void
onGotInviteeID( void* closure, const char* mqttid )
{
MQTTDevID remoteDevID;
if ( strToMQTTCDevID( mqttid, &remoteDevID ) ) {
CAST_GS(GameState*, gs, closure);
2021-02-24 01:00:19 +01:00
sendInviteTo( gs, &remoteDevID );
} else if (!!mqttid) {
2021-02-24 01:00:19 +01:00
call_alert( "MQTT id looks badly formed" );
}
}
2021-02-24 01:00:19 +01:00
typedef struct _KnownSelState {
GameState* gs;
const char** names;
XP_U16 nNames;
} KnownSelState;
2021-02-24 01:00:19 +01:00
static void
onKnownSelected( void* closure, const char* name )
{
XP_LOGFF( "(name=%s)", name );
KnownSelState* kss = (KnownSelState*)closure;
GameState* gs = kss->gs;
if ( 0 == strcmp( BUTTON_CANCEL, name ) ) {
call_get_string( "Invitee's MQTT Device ID?", "",
onGotInviteeID, gs );
} else {
2021-02-24 01:00:19 +01:00
CommsAddrRec addr;
if ( kplr_getAddr( gs->globals->dutil, NULL, name, &addr, NULL ) ) {
sendInviteTo(gs, &addr.u.mqtt.devID);
}
}
XP_FREE( gs->globals->mpool, kss->names );
XP_FREE( gs->globals->mpool, kss );
}
static void
handleInvite( GameState* gs )
{
if ( isVisible(gs) ) {
Globals* globals = gs->globals;
XW_DUtilCtxt* dutil = globals->dutil;
if ( kplr_havePlayers( dutil, NULL ) ) {
KnownSelState* kss = XP_MALLOC( globals->mpool, sizeof(*kss) );
kss->gs = gs;
kss->nNames = 0;
kplr_getNames( dutil, NULL, NULL, &kss->nNames );
kss->names = XP_CALLOC( globals->mpool,
(kss->nNames + 2) * sizeof(kss->names[0]) );
kplr_getNames( dutil, NULL, kss->names, &kss->nNames );
kss->names[kss->nNames] = BUTTON_CANCEL;
XP_ASSERT( NULL == kss->names[kss->nNames+1] );
const char* msg = "Pick an invitee you've played before, "
"or cancel to enter manually.";
call_dialog( msg, kss->names, onKnownSelected, kss );
} else {
call_get_string( "Invitee's MQTT Device ID?", "",
onGotInviteeID, gs );
}
}
}
2021-02-19 04:20:20 +01:00
static void
onGameButton( void* closure, const char* button )
{
if ( !!button ) {
CAST_GS(GameState*, gs, closure);
2021-02-19 04:20:20 +01:00
XP_Bool draw = XP_FALSE;
BoardCtxt* board = gs->game.board;
2021-02-19 04:20:20 +01:00
XP_Bool redo;
if ( 0 == strcmp(button, BUTTON_HINTDOWN ) ) {
draw = board_requestHint( board, NULL, XP_TRUE, &redo );
} else if ( 0 == strcmp(button, BUTTON_HINTUP) ) {
draw = board_requestHint( board, NULL, XP_FALSE, &redo );
} else if ( 0 == strcmp(button, BUTTON_TRADE ) ) {
draw = board_beginTrade( board, NULL );
} else if ( 0 == strcmp(button, BUTTON_STOPTRADE ) ) {
draw = board_endTrade( board );
} else if ( 0 == strcmp(button, BUTTON_COMMIT) ) {
draw = board_commitTurn( board, NULL, XP_FALSE, XP_FALSE, NULL );
} else if ( 0 == strcmp(button, BUTTON_FLIP) ) {
draw = board_flip( board );
} else if ( 0 == strcmp(button, BUTTON_REDO) ) {
draw = board_redoReplacedTiles( board, NULL )
|| board_replaceTiles( board, NULL );
} else if ( 0 == strcmp(button, BUTTON_VALS) ) {
Globals* globals = gs->globals;
2021-02-19 04:20:20 +01:00
globals->cp.tvType = (globals->cp.tvType + 1) % TVT_N_ENTRIES;
draw = board_prefsChanged( board, &globals->cp );
} else if ( 0 == strcmp(button, BUTTON_INVITE) ) {
2021-02-24 01:00:19 +01:00
handleInvite(gs);
2021-02-19 04:20:20 +01:00
}
if ( draw ) {
updateScreen( gs, true );
2021-02-19 04:20:20 +01:00
}
}
}
static void
2021-02-24 02:05:13 +01:00
updateGameButtons( Globals* globals )
2021-02-19 04:20:20 +01:00
{
2021-02-24 02:05:13 +01:00
GameState* gs = getCurGame(globals);
2021-02-19 04:20:20 +01:00
const char* buttons[MAX_BUTTONS];
int cur = 0;
2021-02-24 02:05:13 +01:00
if ( gs && !!gs->util ) {
XP_U16 nPending = server_getPendingRegs( gs->game.server );
2021-02-20 16:27:43 +01:00
if ( 0 < nPending ) {
buttons[cur++] = BUTTON_INVITE;
} else {
GameStateInfo gsi;
game_getState( &gs->game, NULL, &gsi );
2021-02-19 04:20:20 +01:00
2021-02-20 16:27:43 +01:00
if ( gsi.canHint ) {
buttons[cur++] = BUTTON_HINTDOWN;
buttons[cur++] = BUTTON_HINTUP;
}
2021-02-19 04:20:20 +01:00
2021-02-20 16:27:43 +01:00
if ( gsi.inTrade ) {
buttons[cur++] = BUTTON_STOPTRADE;
} else if ( gsi.canTrade ) {
buttons[cur++] = BUTTON_TRADE;
}
buttons[cur++] = BUTTON_COMMIT;
buttons[cur++] = BUTTON_FLIP;
2021-02-19 04:20:20 +01:00
2021-02-20 16:27:43 +01:00
if ( gsi.canRedo ) {
buttons[cur++] = BUTTON_REDO;
}
2021-02-20 16:27:43 +01:00
buttons[cur++] = BUTTON_VALS;
}
}
2021-02-19 04:20:20 +01:00
2021-02-24 02:05:13 +01:00
buttons[cur++] = NULL;
setButtons( BUTTONS_ID_GAME, buttons, onGameButton, gs );
2021-02-19 04:20:20 +01:00
}
static bool
langNameFor( Globals* globals, XP_LangCode code, char buf[], size_t buflen )
{
const char* lc = lcToLocale( code );
const XP_UCHAR* keys[] = { KEY_DICTS, lc, KEY_LANG_NAME, NULL };
XP_U32 len = buflen;
dutil_loadPtr( globals->dutil, NULL, keys, buf, &len );
return 0 < len;
}
static void
showName( GameState* gs )
{
2021-03-08 16:54:05 +01:00
const char* title = NULL;
if ( !!gs ) {
Globals* globals = gs->globals;
title = gs->gameName;
char buf[64];
2021-03-09 16:45:33 +01:00
if ( 1 < countDicts( globals ) ) {
2021-03-08 16:54:05 +01:00
char langName[32];
if ( !langNameFor( globals, gs->gi.dictLang, langName, sizeof(langName) ) ) {
strcpy( langName, "??" );
2021-03-08 16:54:05 +01:00
}
sprintf( buf, "%s (%s)", title, langName );
2021-03-08 16:54:05 +01:00
title = buf;
2021-03-08 16:39:47 +01:00
}
}
show_name( title );
}
static void
onGameRanamed( void* closure, const char* newName )
{
if ( !!newName ) {
CAST_GS(GameState*, gs, closure);
nameGame( gs, newName );
showName( gs );
}
}
typedef struct _NameIterState {
Globals* globals;
int count;
char** names;
char** ids;
} NameIterState;
static void
onGameChosen( void* closure, const char* key )
{
CAST_GLOB(Globals*, globals, closure);
/* To be safe, let's make sure the game exists. We don't want to create
* another if somehow it doesn't */
int gameID;
unformatGameID( &gameID, key );
if ( !!getSavedGame(globals, gameID) ) {
loadAndDraw( globals, NULL, gameID, NULL );
}
2021-02-27 00:45:52 +01:00
updateDeviceButtons( globals );
}
static char*
formatForGame(Globals* globals, bool multiLangs, const XP_UCHAR* gameKey )
{
const XP_UCHAR* keys[] = {KEY_GAMES, gameKey, KEY_NAME, NULL};
char gameName[32];
XP_U32 len = sizeof(gameName);
dutil_loadPtr( globals->dutil, NULL, keys, gameName, &len );
char buf[256];
int offset = snprintf( buf, sizeof(buf), "%s", gameName );
GameSummary summary;
len = sizeof(summary);
keys[2] = KEY_SUMMARY;
dutil_loadPtr( globals->dutil, NULL, keys, &summary, &len );
if ( len == sizeof(summary) ) {
if ( multiLangs ) {
char langName[32];
if ( langNameFor( globals, summary.lang, langName, sizeof(langName) ) ) {
offset += snprintf( buf+offset, sizeof(buf)-offset, " (in %s)", langName );
}
}
offset += snprintf( buf+offset, sizeof(buf)-offset, " Opponent: %s",
summary.opponents );
2021-03-10 05:13:42 +01:00
offset += snprintf( buf+offset, sizeof(buf)-offset, " My turn: %s",
0 <= summary.turn && summary.turnIsLocal ? "YES" : "NO" );
}
char* result = NULL;
replaceStringIfDifferent( globals->mpool, &result, buf );
return result;
}
static XP_Bool
2021-03-05 18:03:32 +01:00
onOneGameName( void* closure, const XP_UCHAR* keys[] )
{
const char* gameIDStr = keys[1];
if ( 0 != strcmp( gameIDStr, "0" ) ) { /* temporary */
NameIterState* nis = (NameIterState*)closure;
Globals* globals = nis->globals;
bool multiLangs = 1 < countDicts(globals);
2021-03-05 18:03:32 +01:00
/* Make sure game exists. This may be unnecessary later */
const XP_UCHAR* dataKeys[] = { keys[0], keys[1], KEY_GAME, NULL };
XP_U32 dataLen;
dutil_loadPtr( globals->dutil, NULL, dataKeys, NULL, &dataLen );
if ( 0 < dataLen ) {
int cur = nis->count++;
nis->names = XP_REALLOC( globals->mpool, nis->names,
nis->count * sizeof(nis->names[0]) );
nis->names[cur] = formatForGame( globals, multiLangs, keys[1] );
2021-03-05 18:03:32 +01:00
nis->ids = XP_REALLOC( globals->mpool, nis->ids,
nis->count * sizeof(nis->ids[0]) );
nis->ids[cur] = XP_MALLOC( globals->mpool, 1 + strlen(gameIDStr) );
strcpy( nis->ids[cur], gameIDStr );
}
}
return true; /* keep going */
}
2021-02-19 20:19:16 +01:00
static void
pickGame( Globals* globals )
2021-02-19 20:19:16 +01:00
{
XW_DUtilCtxt* dutil = globals->dutil;
NameIterState nis = { .globals = globals, };
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = {KEY_GAMES, KEY_WILDCARD, KEY_NAME, NULL};
dutil_forEach( dutil, NULL, keys, onOneGameName, &nis );
const char* msg = "Choose game to open:";
call_pickGame(msg, nis.ids, nis.names, nis.count, onGameChosen, globals);
2021-03-09 07:27:55 +01:00
for ( int ii = 0; ii < nis.count; ++ii ) {
XP_FREE( globals->mpool, nis.names[ii] );
XP_FREE( globals->mpool, nis.ids[ii] );
}
XP_FREE( globals->mpool, nis.names );
XP_FREE( globals->mpool, nis.ids );
2021-02-19 20:19:16 +01:00
}
static void
cleanupGame( GameState* gs )
{
if ( !!gs->util ) {
game_dispose( &gs->game, NULL );
gi_disposePlayerInfo( MPPARM(gs->globals->mpool) &gs->gi );
wasm_util_destroy( gs->util );
gs->util = NULL;
// XP_MEMSET( &gs, 0, sizeof(globals->gs) );
}
}
static void
deleteGame( GameState* gs )
{
Globals* globals = gs->globals;
2021-02-24 02:05:13 +01:00
if ( globals->curGame == gs ) {
globals->curGame = NULL;
}
updateGameButtons( globals );
int gameID = gs->gi.gameID; /* remember it */
cleanupGame( gs );
// Remove from linked list, but don't actually free because could live in
// a js-side object still. Needs refcounting or somesuch
GameState** prev = &globals->games;
for ( GameState* cur = globals->games; !!cur; cur = cur->next ) {
if ( gs == cur ) {
*prev = cur->next;
break;
}
prev = &cur->next;
}
2021-03-05 18:03:32 +01:00
char gameIDStr[16];
formatGameID( gameIDStr, sizeof(gameIDStr), gameID );
const XP_UCHAR* keys[] = {KEY_GAMES, gameIDStr, NULL};
dutil_remove( globals->dutil, keys );
2021-03-08 16:54:05 +01:00
showName( NULL );
}
static void
onDeleteConfirmed( void* closure, bool confirmed )
{
if ( confirmed ) {
CAST_GS(GameState*, gs, closure);
Globals* globals = gs->globals;
deleteGame( gs );
clearScreen( globals );
2021-02-27 00:45:52 +01:00
updateDeviceButtons( globals );
}
}
static bool
getLocalName( Globals* globals, char* playerName, size_t buflen )
2021-03-05 18:03:32 +01:00
{
XP_U32 len = buflen;
const XP_UCHAR* keys[] = {KEY_PLAYER_NAME, NULL};
dutil_loadPtr( globals->dutil, NULL, keys, playerName, &len );
bool haveName = 0 != len;
if ( !haveName ) { /* not found? */
2021-03-05 18:03:32 +01:00
strcpy( playerName, "Player 1" );
}
return haveName;
2021-03-05 18:03:32 +01:00
}
static void
onPlayerNamed( void* closure, const char* name )
{
CAST_GLOB(Globals*, globals, closure);
if ( !!name ) {
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = {KEY_PLAYER_NAME, NULL};
dutil_storePtr( globals->dutil, NULL, keys, (void*)name, 1 + strlen(name) );
}
}
typedef struct _NewGameState {
Globals* globals;
char* langs[16];
int nLangs;
} NewGameState;
static XP_Bool
onOneLang( void* closure, const XP_UCHAR* keys[] )
{
NewGameState* ngs = (NewGameState*)closure;
char langName[32];
XP_U32 len = sizeof(langName);
dutil_loadPtr( ngs->globals->dutil, NULL, keys, langName, &len );
replaceStringIfDifferent( ngs->globals->mpool, &ngs->langs[ngs->nLangs], langName );
XP_LOGFF( "set langs[%d] %s", ngs->nLangs, ngs->langs[ngs->nLangs] );
++ngs->nLangs;
XP_ASSERT( ngs->nLangs < VSIZE(ngs->langs));
return true;
}
static void
callNewGame( Globals* globals )
{
NewGameState ngs = {.globals = globals};
const XP_UCHAR* keys[] = {KEY_DICTS, KEY_WILDCARD, KEY_LANG_NAME, NULL};
dutil_forEach( globals->dutil, NULL, keys, onOneLang, &ngs );
js_callNewGame("Configure your new game", globals, ngs.langs, ngs.nLangs);
}
2021-02-19 04:20:20 +01:00
static void
onDeviceButton( void* closure, const char* button )
{
CAST_GLOB(Globals*, globals, closure);
2021-02-19 04:20:20 +01:00
XP_LOGFF( "(button=%s)", button );
if ( 0 == strcmp(button, BUTTON_GAME_NEW) ) {
callNewGame(globals);
2021-03-09 20:01:38 +01:00
} else if ( 0 == strcmp(button, BUTTON_GAME_GAMES) ) {
pickGame( globals );
2021-02-19 04:20:20 +01:00
} else if ( 0 == strcmp(button, BUTTON_GAME_RENAME ) ) {
GameState* curGS = getCurGame( globals );
ensureName( curGS );
call_get_string( "Rename your game", curGS->gameName,
onGameRanamed, curGS );
2021-02-19 04:20:20 +01:00
} else if ( 0 == strcmp(button, BUTTON_GAME_DELETE) ) {
GameState* curGS = getCurGame( globals );
char msg[256];
snprintf( msg, sizeof(msg), "Are you sure you want to delete the game \"%s\"?"
"\nThis action cannot be undone.",
curGS->gameName );
call_confirm( globals, msg, onDeleteConfirmed, curGS );
} else if ( 0 == strcmp(button, BUTTON_NAME ) ) {
2021-03-05 18:03:32 +01:00
char playerName[32];
getLocalName( globals, playerName, sizeof(playerName)-1 );
2021-03-05 18:03:32 +01:00
call_get_string( "Set your (local) player name", playerName,
onPlayerNamed, globals );
2021-02-19 04:20:20 +01:00
}
}
2021-02-27 00:45:52 +01:00
static XP_Bool
2021-03-05 18:03:32 +01:00
upCounter( void* closure, const XP_UCHAR* keys[] )
2021-02-27 00:45:52 +01:00
{
int* intp = (int*)closure;
++*intp;
return true;
}
static int
countGames( Globals* globals )
{
int nFound = 0;
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = {KEY_GAMES, KEY_WILDCARD, KEY_GAME, NULL};
dutil_forEach( globals->dutil, NULL, keys, upCounter, &nFound );
2021-02-27 00:45:52 +01:00
return nFound;
}
static int
countDicts( Globals* globals )
{
int count = 0;
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = {KEY_DICTS, KEY_WILDCARD, NULL};
dutil_forEach( globals->dutil, NULL, keys, upCounter, &count );
LOG_RETURNF( "%d", count );
return count;
}
static bool
haveDictFor(Globals* globals, const char* lc)
{
int count = 0;
const XP_UCHAR* keys[] = {KEY_DICTS, lc, NULL};
dutil_forEach( globals->dutil, NULL, keys, upCounter, &count );
return 0 < count;
}
2021-02-19 04:20:20 +01:00
static void
updateDeviceButtons( Globals* globals )
{
const char* buttons[MAX_BUTTONS];
int cur = 0;
2021-02-27 00:45:52 +01:00
if ( 0 < countGames(globals) ) {
2021-03-09 20:01:38 +01:00
buttons[cur++] = BUTTON_GAME_GAMES;
2021-02-27 00:45:52 +01:00
}
if ( 0 < countDicts( globals ) ) {
buttons[cur++] = BUTTON_GAME_NEW;
}
2021-02-27 00:45:52 +01:00
if ( !!getCurGame( globals ) ) {
buttons[cur++] = BUTTON_GAME_RENAME;
buttons[cur++] = BUTTON_GAME_DELETE;
}
buttons[cur++] = BUTTON_NAME;
2021-02-19 04:20:20 +01:00
buttons[cur++] = NULL;
XP_ASSERT( cur <= VSIZE(buttons) );
2021-02-19 04:20:20 +01:00
setButtons( BUTTONS_ID_DEVICE, buttons, onDeviceButton, globals );
2021-02-11 00:46:52 +01:00
}
static void
onConflict( void* closure, const char* ignored )
{
CAST_GLOB(Globals*, globals, closure);
call_alert( "Control passed to another tab" );
XP_MEMSET( globals, 0, sizeof(*globals) ); /* stop everything :-) */
}
static void
2021-03-10 05:13:42 +01:00
onFocussed( void* closure, const char* newState )
{
2021-03-10 05:13:42 +01:00
CAST_GLOB(Globals*, globals, closure);
globals->focussed = 0 == strcmp("focus", newState);
XP_LOGFF( "focussed now: %d", globals->focussed );
/* This hasn't worked.... */
2021-03-10 05:13:42 +01:00
GameState* gs = getCurGame( globals );
if ( !!gs ) {
board_invalAll( gs->game.board );
updateScreen( gs, false );
}
}
static void
onMqttMsg(void* closure, const uint8_t* data, int len )
{
CAST_GLOB(Globals*, globals, closure);
dvc_parseMQTTPacket( globals->dutil, NULL, data, len );
}
2021-03-08 05:51:22 +01:00
static bool
2021-03-08 21:46:42 +01:00
storeAsDict( Globals* globals, GotDictData* gdd )
{
char shortName[32];
sprintf( shortName, "%s", gdd->dictName );
char* dot = strstr(shortName, ".xwd");
if ( !!dot ) {
*dot = '\0';
}
XP_LOGFF("shortName: %s", shortName);
2021-03-08 05:51:22 +01:00
/* First make a dict of it. If it doesn't work out, don't store the
data! */
DictionaryCtxt* dict =
wasm_dictionary_make( globals, NULL, shortName, gdd->data, gdd->len );
2021-03-08 05:51:22 +01:00
bool success = !!dict;
if ( success ) {
dict_unref( dict, NULL );
const XP_UCHAR* keys[] = {KEY_DICTS, gdd->lc, shortName, NULL};
dutil_storePtr( globals->dutil, NULL, keys, gdd->data, gdd->len );
2021-03-08 16:39:47 +01:00
keys[2] = KEY_LANG_NAME;
dutil_storePtr( globals->dutil, NULL, keys, gdd->langName,
strlen(gdd->langName) + 1 );
2021-03-08 21:46:42 +01:00
char msg[128];
sprintf( msg, "Successfull installed wordlist for play in %s.",
gdd->langName );
call_alert( msg );
2021-03-08 05:51:22 +01:00
}
LOG_RETURNF( "%d", success );
return success;
}
/* static void */
/* onGotDict( void* closure, const char* lc, const char* langName, */
/* const char* dictName, uint8_t* data, int len) */
/* { */
/* CAST_GLOB(Globals*, globals, closure); */
/* if ( storeAsDict( globals, lc, langName, dictName, data, len ) ) { */
/* if ( 0 == countGames(globals) ) { */
/* loadAndDraw( globals, NULL, NULL, NULL ); */
/* } */
/* updateDeviceButtons( globals ); */
/* } */
/* } */
2021-02-02 05:13:25 +01:00
static void
initDeviceGlobals( Globals* globals )
2021-02-02 05:13:25 +01:00
{
globals->cp.showBoardArrow = XP_TRUE;
2021-02-02 22:49:27 +01:00
globals->cp.allowPeek = XP_TRUE;
2021-02-20 02:49:20 +01:00
globals->cp.showRobotScores = XP_TRUE;
2021-02-03 04:20:26 +01:00
globals->cp.sortNewTiles = XP_TRUE;
globals->cp.showColors = XP_TRUE;
2021-02-02 05:13:25 +01:00
2021-02-07 21:48:06 +01:00
globals->procs.send = send_msg;
globals->procs.closure = globals;
#ifdef MEMDEBUG
globals->mpool = mpool_make( "wasm" );
#endif
globals->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(globals->mpool) );
globals->dutil = wasm_dutil_make( MPPARM(globals->mpool) globals->vtMgr, globals );
globals->dictMgr = dmgr_make( MPPARM_NOCOMMA(globals->mpool) );
globals->draw = wasm_draw_make( MPPARM(globals->mpool)
WINDOW_WIDTH, WINDOW_HEIGHT );
2021-02-04 01:19:16 +01:00
MQTTDevID devID;
dvc_getMQTTDevID( globals->dutil, NULL, &devID );
2021-02-07 21:48:06 +01:00
XP_UCHAR buf[32];
XP_SNPRINTF( buf, VSIZE(buf), MQTTDevID_FMT, devID );
XP_LOGFF( "got mqtt devID: %s", buf );
2021-02-22 06:59:03 +01:00
int now = dutil_getCurSeconds( globals->dutil, NULL );
2021-03-09 20:39:43 +01:00
bool dbg =
#ifdef DEBUG
true
#else
false
#endif
;
call_setup( globals, dbg, buf, GITREV, now, onConflict, onFocussed, onMqttMsg );
2021-02-07 21:48:06 +01:00
}
static void
storeCurOpen( GameState* gs )
2021-02-07 21:48:06 +01:00
{
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = {KEY_LAST_GID, NULL};
dutil_storePtr( gs->globals->dutil, NULL, keys,
&gs->gi.gameID, sizeof(gs->gi.gameID) );
}
static void
startGame( GameState* gs, const char* name )
{
gs->globals->curGame = gs;
ensureName( gs );
XP_LOGFF( "changed curGame to %s", gs->gameName );
showName( gs );
storeCurOpen( gs );
2021-02-07 21:48:06 +01:00
BoardDims dims;
board_figureLayout( gs->game.board, NULL, &gs->gi,
2021-02-07 21:48:06 +01:00
WASM_BOARD_LEFT, WASM_HOR_SCORE_TOP, BDWIDTH, BDHEIGHT,
110, 150, 200, BDWIDTH-25, BDWIDTH/15, BDHEIGHT/15,
XP_FALSE, &dims );
board_applyLayout( gs->game.board, NULL, &dims );
2021-02-07 21:48:06 +01:00
board_invalAll( gs->game.board ); /* redraw screen on loading new game */
if ( SERVER_ISCLIENT == gs->gi.serverRole ) {
2021-02-17 00:32:33 +01:00
if ( !!name ) {
replaceStringIfDifferent( gs->globals->mpool,
&gs->gi.players[0].name,
2021-02-17 00:32:33 +01:00
name );
}
server_initClientConnection( gs->game.server, NULL );
2021-02-07 21:48:06 +01:00
}
(void)server_do( gs->game.server, NULL ); /* assign tiles, etc. */
if ( !!gs->game.comms ) {
comms_resendAll( gs->game.comms, NULL, COMMS_CONN_MQTT, XP_TRUE );
2021-02-10 02:53:30 +01:00
}
2021-02-07 21:48:06 +01:00
updateScreen( gs, true );
2021-02-07 21:48:06 +01:00
LOG_RETURN_VOID();
}
static GameState*
newFromInvite( Globals* globals, const NetLaunchInfo* invite )
2021-02-15 23:59:44 +01:00
{
GameState* gs = newGameState(globals);
gs->util = wasm_util_make( MPPARM(globals->mpool) &gs->gi,
globals->dutil, gs );
2021-02-15 23:59:44 +01:00
2021-03-05 18:03:32 +01:00
char playerName[32];
getLocalName( globals, playerName, sizeof(playerName) );
2021-03-05 18:03:32 +01:00
2021-02-20 18:04:57 +01:00
game_makeFromInvite( MPPARM(globals->mpool) NULL, invite,
2021-03-05 18:03:32 +01:00
&gs->game, &gs->gi, playerName,
gs->util, globals->draw,
2021-02-20 18:04:57 +01:00
&globals->cp, &globals->procs );
2021-02-23 18:04:56 +01:00
if ( invite->gameName[0] ) {
nameGame( gs, invite->gameName );
2021-02-23 18:04:56 +01:00
}
ensureName( gs );
return gs;
}
2021-02-15 23:59:44 +01:00
typedef struct _DictDownState {
Globals* globals;
NetLaunchInfo invite;
const char* lc;
} DictDownState;
static void
onDictForInvite( void* closure, GotDictData* gdd )
{
DictDownState* dds = (DictDownState*)closure;
if ( !!gdd->data
&& 0 < gdd->len
&& storeAsDict( dds->globals, gdd ) ) {
loadAndDraw( dds->globals, &dds->invite, 0, NULL );
} else {
char msg[128];
sprintf( msg, "Unable to download %s worldlist for invitation", dds->lc );
call_alert( msg );
}
XP_FREE( dds->globals->mpool, dds );
}
static void
onDictConfirmed( void* closure, bool confirmed )
{
DictDownState* dds = (DictDownState*)closure;
if ( confirmed ) {
call_get_dict( dds->lc, onDictForInvite, dds );
} else {
XP_FREE( dds->globals->mpool, dds );
}
}
/* If you launch a URL that encodes an invitation, you'll get here. If it's
* the first time (the game hasn't been created yet) you'll get a new game
* that connects to the host. If you've already created the game, you'll be
* taken to it in whatever state it's in. If you've deleted the game, bad
* situation: you'll get a new game that will be unable to connect to any
* host.
*/
static GameState*
gameFromInvite( Globals* globals, const NetLaunchInfo* invite )
{
bool needsLoad = true;
GameState* gs = getSavedGame( globals, invite->gameID );
if ( !gs ) {
const char* lc = lcToLocale( invite->lang );
if ( haveDictFor(globals, lc) ) {
gs = newFromInvite( globals, invite );
} else {
char msg[128];
sprintf( msg, "Invitation requires a wordlist %s for "
2021-03-08 16:54:05 +01:00
"locale %s; download now?", invite->dict, lc );
DictDownState* dds = XP_MALLOC( globals->mpool, sizeof(*dds) );
dds->globals = globals;
dds->invite = *invite;
dds->lc = lc;
call_confirm(globals, msg, onDictConfirmed, dds);
}
2021-02-20 18:04:57 +01:00
}
return gs;
2021-02-20 18:04:57 +01:00
}
2021-02-15 23:59:44 +01:00
static GameState*
newGameState( Globals* globals )
2021-02-20 18:04:57 +01:00
{
GameState* gs = XP_CALLOC( globals->mpool, sizeof(*gs) );
gs->globals = globals;
#ifdef DEBUG
gs->_GUARD = GUARD_GS;
#endif
gs->next = globals->games;
globals->games = gs;
2021-02-20 18:04:57 +01:00
return gs;
}
2021-02-15 23:59:44 +01:00
static GameState*
getCurGame( Globals* globals )
{
return globals->curGame;
2021-02-15 23:59:44 +01:00
}
static void
removeGameState( GameState* gs )
{
XP_ASSERT(0);
}
2021-02-17 00:32:33 +01:00
static GameState*
getSavedGame( Globals* globals, int gameID )
{
GameState* gs;
/* Is it already open? */
for ( gs = globals->games; !!gs; gs = gs->next ) {
if ( gameID == gs->gi.gameID ) {
break;
2021-02-10 21:18:15 +01:00
}
}
if ( !gs ) {
gs = newGameState( globals );
2021-02-10 21:18:15 +01:00
XP_LOGFF( "gameID: %X", gameID );
bool loaded = false;
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
globals->vtMgr );
2021-03-05 18:03:32 +01:00
char gameIDStr[16];
formatGameID( gameIDStr, sizeof(gameIDStr), gameID );
const XP_UCHAR* keys[] = {KEY_GAMES, gameIDStr, KEY_GAME, NULL};
dutil_loadStream( globals->dutil, NULL, keys, stream );
if ( 0 < stream_getSize( stream ) ) {
XP_ASSERT( !gs->util );
gs->util = wasm_util_make( MPPARM(globals->mpool) &gs->gi,
globals->dutil, gs );
XP_LOGFF( "there's a saved game!!" );
loaded = game_makeFromStream( MPPARM(globals->mpool) NULL, stream,
&gs->game, &gs->gi,
gs->util, globals->draw,
&globals->cp, &globals->procs );
if ( loaded ) {
loadName( gs );
ensureName( gs );
updateScreen( gs, false );
} else {
removeGameState( gs );
gs = NULL;
}
} else {
2021-03-05 18:03:32 +01:00
XP_LOGFF( "ERROR: no saved data for game %s", gameIDStr );
XP_ASSERT( globals->games == gs );
globals->games = gs->next;
XP_FREE( globals->mpool, gs );
gs = NULL;
2021-02-10 21:18:15 +01:00
}
stream_destroy( stream, NULL );
2021-02-10 21:18:15 +01:00
}
XP_LOGFF( "(%X) => %p", gameID, gs );
return gs;
}
static void
saveName( GameState* gs )
2021-02-19 20:19:16 +01:00
{
2021-03-05 18:03:32 +01:00
char gameIDStr[32];
formatGameID( gameIDStr, sizeof(gameIDStr), gs->gi.gameID );
const XP_UCHAR* keys[] = {KEY_GAMES, gameIDStr, KEY_NAME, NULL};
dutil_storePtr( gs->globals->dutil, NULL, keys,
(XP_U8*)gs->gameName, 1 + strlen(gs->gameName) );
2021-02-19 20:19:16 +01:00
}
static void
loadName( GameState* gs )
2021-02-19 20:19:16 +01:00
{
2021-03-05 18:03:32 +01:00
char gameIDStr[32];
formatGameID( gameIDStr, sizeof(gameIDStr), gs->gi.gameID );
XP_U32 len = sizeof(gs->gameName);
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = {KEY_GAMES, gameIDStr, KEY_NAME, NULL};
dutil_loadPtr( gs->globals->dutil, NULL, keys, (XP_U8*)gs->gameName, &len );
2021-02-19 20:19:16 +01:00
}
static void
ensureName( GameState* gs )
2021-02-19 20:19:16 +01:00
{
if ( '\0' == gs->gameName[0] ) {
nameGame( gs, NULL );
2021-02-19 20:19:16 +01:00
}
}
static int
getNextGameNo( Globals* globals )
{
int val = 0;
2021-03-05 18:03:32 +01:00
const XP_UCHAR* keys[] = { KEY_NEXT_GAME, NULL };
XP_U32 len = sizeof(val);
2021-03-05 18:03:32 +01:00
dutil_loadPtr( globals->dutil, NULL, keys, (XP_U8*)&val, &len );
++val;
2021-03-05 18:03:32 +01:00
dutil_storePtr( globals->dutil, NULL, keys, (XP_U8*)&val, sizeof(val) );
XP_LOGFF( "getNextGameNo() => %d", val );
return val;
}
2021-02-19 20:19:16 +01:00
static void
nameGame( GameState* gs, const char* name )
{
if ( !!name ) {
snprintf( gs->gameName, sizeof(gs->gameName), "%s", name );
} else {
snprintf( gs->gameName, sizeof(gs->gameName),
"Game %d", getNextGameNo(gs->globals) );
}
saveName( gs );
XP_LOGFF( "named game: %s", gs->gameName );
}
typedef struct _FindOneState {
Globals* globals;
DictionaryCtxt* dict;
} FindOneState;
static XP_Bool
2021-03-05 18:03:32 +01:00
onOneDict( void* closure, const XP_UCHAR* keys[] )
{
2021-03-05 18:03:32 +01:00
const XP_UCHAR* dictName = keys[2];
XP_LOGFF( "dictName: %s", dictName );
FindOneState* fos = (FindOneState*)closure;
2021-03-05 18:03:32 +01:00
XP_ASSERT( !fos->dict );
XW_DUtilCtxt* dutil = fos->globals->dutil;
XP_U32 len;
2021-03-05 18:03:32 +01:00
dutil_loadPtr( dutil, NULL, keys, NULL, &len );
uint8_t* ptr = XP_MALLOC( fos->globals->mpool, len );
2021-03-05 18:03:32 +01:00
dutil_loadPtr( dutil, NULL, keys, ptr, &len );
fos->dict = wasm_dictionary_make( fos->globals, NULL, dictName, ptr, len );
XP_FREE( fos->globals->mpool, ptr );
return XP_FALSE;
}
static DictionaryCtxt*
loadAnyDict( Globals* globals, const char* lc )
{
FindOneState fos = {.globals = globals,
};
const XP_UCHAR* keys[] = {KEY_DICTS, lc, KEY_WILDCARD, NULL};
2021-03-05 18:03:32 +01:00
dutil_forEach( globals->dutil, NULL, keys, onOneDict, &fos );
LOG_RETURNF( "%p", fos.dict );
return fos.dict;
}
typedef struct _FindLCState {
Globals* globals;
const char* langName;
char* lc;
} FindLCState;
static XP_Bool
onOneLangName( void* closure, const XP_UCHAR* keys[] )
{
bool found = false;
FindLCState* lcs = (FindLCState*)closure;
char langName[32];
XP_U32 len = sizeof(langName);
dutil_loadPtr( lcs->globals->dutil, NULL, keys, langName, &len );
if ( 0 == strcmp( lcs->langName, langName ) ) {
strcpy( lcs->lc, keys[1] );
found = true;
}
return !found;
}
static void
langNameToLC(Globals* globals, const char* langName, char lc[], size_t len)
{
FindLCState lcc = { .globals = globals,
.lc = lc,
.langName = langName,
};
const XP_UCHAR* keys[] = {KEY_DICTS, KEY_WILDCARD, KEY_LANG_NAME, NULL};
dutil_forEach( globals->dutil, NULL, keys, onOneLangName, &lcc );
}
static void
2021-02-10 21:18:15 +01:00
loadAndDraw( Globals* globals, const NetLaunchInfo* invite,
int gameID, NewGameParams* params )
{
XP_LOGFF( "(gameID: %X)", gameID );
GameState* gs = NULL;
2021-02-10 21:18:15 +01:00
bool haveGame;
if ( !params ) {
2021-02-10 21:18:15 +01:00
/* First, load any saved game. We need it e.g. to confirm that an incoming
invite is a dup and should be dropped. */
if ( 0 != gameID ) {
gs = getSavedGame( globals, gameID );
}
if ( !!invite ) { /* overwrite gs is ok: we'll likely want both games */
gs = gameFromInvite( globals, invite );
2021-02-10 21:18:15 +01:00
}
}
2021-02-02 05:13:25 +01:00
if ( !gs ) {
const char* lc = NULL == params ? "en" : params->lc;
DictionaryCtxt* dict = loadAnyDict( globals, lc );
if ( !!dict ) {
2021-03-05 18:03:32 +01:00
char playerName[32];
getLocalName( globals, playerName, sizeof(playerName) );
2021-03-05 18:03:32 +01:00
gs = newGameState( globals );
gs->gi.serverRole = !!params && !params->isRobotNotRemote
? SERVER_ISSERVER : SERVER_STANDALONE;
gs->gi.phoniesAction = PHONIES_WARN;
gs->gi.hintsNotAllowed = !!params && params->hintsNotAllowed || false;
gs->gi.gameID = 0;
gs->gi.dictLang = dict_getLangCode(dict);
replaceStringIfDifferent( globals->mpool, &gs->gi.dictName,
dict_getShortName(dict) );
gs->gi.nPlayers = 2;
gs->gi.boardSize = 15;
2021-03-05 18:03:32 +01:00
gs->gi.players[0].name = copyString( globals->mpool, playerName );
gs->gi.players[0].isLocal = XP_TRUE;
gs->gi.players[0].robotIQ = 0;
2021-03-10 05:13:42 +01:00
bool otherLocal = !!params ? params->isRobotNotRemote : true;
gs->gi.players[1].name = copyString( globals->mpool,
otherLocal ? "Robot" : "(remote)" );
gs->gi.players[1].isLocal = otherLocal;
XP_LOGFF( "set isLocal[1]: %d", gs->gi.players[1].isLocal );
gs->gi.players[1].robotIQ = 99; /* doesn't matter if remote */
gs->util = wasm_util_make( MPPARM(globals->mpool) &gs->gi,
globals->dutil, gs );
XP_LOGFF( "calling game_makeNewGame()" );
game_makeNewGame( MPPARM(globals->mpool) NULL,
&gs->game, &gs->gi,
gs->util, globals->draw,
&globals->cp, &globals->procs );
ensureName( gs );
if ( !!gs->game.comms ) {
CommsAddrRec addr = {0};
makeSelfAddr( globals, &addr );
comms_augmentHostAddr( gs->game.comms, NULL, &addr );
}
dict_unref( dict, NULL );
}
2021-02-06 18:23:39 +01:00
}
if ( !!gs ) {
startGame( gs, NULL );
}
LOG_RETURN_VOID();
}
static bool
isVisible( GameState* gs )
{
return getCurGame(gs->globals) == gs;
2021-02-07 21:48:06 +01:00
}
2021-02-02 05:13:25 +01:00
2021-02-07 21:48:06 +01:00
void
main_gameFromInvite( Globals* globals, const NetLaunchInfo* invite )
{
GameState* gs = gameFromInvite( globals, invite );
if ( gs ) {
startGame( gs, NULL );
2021-02-07 21:48:06 +01:00
}
}
2021-02-20 18:04:57 +01:00
typedef struct _OpenForMessageState {
Globals* globals;
int gameID;
CommsAddrRec from;
XWStreamCtxt* stream;
} OpenForMessageState;
void
main_onGameMessage( Globals* globals, XP_U32 gameID,
const CommsAddrRec* from, XWStreamCtxt* stream )
{
GameState* gs = getSavedGame( globals, gameID );
if ( !!gs ) {
if ( game_receiveMessage( &gs->game, NULL, stream, from ) ) {
updateScreen( gs, true );
2021-02-15 23:59:44 +01:00
}
2021-03-10 05:13:42 +01:00
if ( !globals->focussed && js_getHaveNotifyPerm() ) {
GameSummary summary;
game_summarize( &gs->game, &gs->gi, &summary );
if ( summary.turnIsLocal ) {
char buf[128];
sprintf( buf, "Your turn in game %s", gs->gameName );
js_notify( buf );
}
}
} else {
char msg[128];
snprintf( msg, sizeof(msg), "Dropping move for deleted game (id: %X/%d)",
gameID, gameID );
call_alert( msg );
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
globals->vtMgr );
dvc_makeMQTTNoSuchGame( globals->dutil, NULL, stream, gameID );
sendStreamToDev( stream, &from->u.mqtt.devID );
}
2021-02-20 16:27:43 +01:00
}
void
main_onGameGone( Globals* globals, XP_U32 gameID )
{
GameState* gs = getSavedGame( globals, gameID );
if ( !!gs ) {
const char* msg = "This game has been deleted on the remote device. "
"Delete here too?";
call_confirm( globals, msg, onDeleteConfirmed, gs );
}
}
2021-02-07 21:48:06 +01:00
void
main_sendOnClose( XWStreamCtxt* stream, XWEnv env, void* closure )
{
CAST_GS(GameState*, gs, closure );
2021-02-07 21:48:06 +01:00
XP_LOGFF( "called with msg of len %d", stream_getSize(stream) );
(void)comms_send( gs->game.comms, NULL, stream );
2021-02-02 05:13:25 +01:00
}
2021-02-01 17:30:34 +01:00
2021-02-10 23:16:22 +01:00
void
main_playerScoreHeld( GameState* gs, XP_U16 player )
2021-02-10 23:16:22 +01:00
{
LastMoveInfo lmi;
XP_UCHAR buf[128];
if ( model_getPlayersLastScore( gs->game.model, NULL, player, &lmi ) ) {
2021-02-10 23:16:22 +01:00
switch ( lmi.moveType ) {
case ASSIGN_TYPE:
XP_SNPRINTF( buf, sizeof(buf), "Tiles assigned to %s", lmi.names[0] );
break;
case MOVE_TYPE:
XP_SNPRINTF( buf, sizeof(buf), "%s formed %s for %d points", lmi.names[0],
lmi.word, lmi.score );
break;
case TRADE_TYPE:
XP_SNPRINTF( buf, sizeof(buf), "%s traded %d tiles", lmi.names[0],
lmi.nTiles );
break;
default:
buf[0] = '\0';
}
}
if ( buf[0] ) {
call_alert( buf );
}
}
2021-02-17 02:22:50 +01:00
void
main_showRemaining( GameState* gs )
2021-02-17 02:22:50 +01:00
{
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(gs->globals->mpool)
gs->globals->vtMgr );
board_formatRemainingTiles( gs->game.board, NULL, stream );
2021-02-17 02:22:50 +01:00
stream_putU8( stream, 0 );
call_alert( (const XP_UCHAR*)stream_getPtr( stream ) );
stream_destroy( stream, NULL );
}
2021-02-13 21:16:09 +01:00
static void
openConfirmed(void* closure, bool confirmed)
{
if ( confirmed ) {
CAST_GS(GameState*, gs, closure);
loadAndDraw( gs->globals, NULL, gs->gi.gameID, NULL );
}
}
void
main_turnChanged( GameState* gs, int newTurn )
{
if ( 0 <= newTurn && !isVisible(gs) && gs->gi.players[newTurn].isLocal ) {
char msg[128];
snprintf( msg, sizeof(msg),
2021-03-10 05:13:42 +01:00
"It's your turn in game \"%s\". Would you like to open it now?",
gs->gameName );
call_confirm( gs->globals, msg, openConfirmed, gs );
}
}
2021-02-18 03:58:46 +01:00
typedef struct _BlankPickState {
GameState* gs;
2021-02-18 03:58:46 +01:00
int col, row;
int nTiles;
int playerNum;
} BlankPickState;
static void
onBlankPicked( void* closure, const char* str )
2021-02-18 03:58:46 +01:00
{
XP_LOGFF( "index: %s", str );
2021-02-18 03:58:46 +01:00
BlankPickState* bps = (BlankPickState*)closure;
GameState* gs = bps->gs;
Globals* globals = gs->globals;
2021-02-18 03:58:46 +01:00
int indx = atoi(str);
if ( 0 <= indx && indx < bps->nTiles
&& board_setBlankValue( gs->game.board, bps->playerNum,
bps->col, bps->row, indx ) ) {
updateScreen( gs, true );
2021-02-18 03:58:46 +01:00
}
XP_FREE( globals->mpool, bps );
2021-02-18 03:58:46 +01:00
}
void
main_pickBlank( GameState* gs, int playerNum, int col, int row,
2021-02-18 03:58:46 +01:00
const char** tileFaces, int nTiles )
{
BlankPickState* bps = XP_MALLOC( gs->globals->mpool, sizeof(*bps) );
bps->gs = gs;
2021-02-18 03:58:46 +01:00
bps->row = row;
bps->col = col;
bps->playerNum = playerNum;
bps->nTiles = nTiles;
call_pickBlank( "Pick a tile for your blank", tileFaces, nTiles,
2021-02-18 03:58:46 +01:00
onBlankPicked, bps );
}
2021-02-13 21:16:09 +01:00
void
main_showGameOver( GameState* gs )
{
if ( isVisible(gs) ) {
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(gs->globals->mpool)
gs->globals->vtMgr );
server_writeFinalScores( gs->game.server, NULL, stream );
stream_putU8( stream, 0 );
call_alert( (const XP_UCHAR*)stream_getPtr( stream ) );
stream_destroy( stream, NULL );
}
2021-02-13 21:16:09 +01:00
}
2021-02-02 22:49:27 +01:00
static time_t
getCurMS()
{
struct timeval tv;
gettimeofday( &tv, NULL );
time_t result = tv.tv_sec * 1000; /* convert to millis */
result += tv.tv_usec / 1000; /* convert to millis too */
// LOG_RETURNF( "%x", result );
return result;
}
2021-02-10 23:16:22 +01:00
void
main_clear_timer( GameState* gs, XWTimerReason why )
2021-02-02 22:49:27 +01:00
{
2021-02-10 23:16:22 +01:00
XP_LOGFF( "why: %d" );
2021-02-10 23:25:25 +01:00
// XP_ASSERT(0); fires when start new game
2021-02-03 04:20:26 +01:00
}
2021-02-10 23:16:22 +01:00
typedef struct _TimerClosure {
GameState* gs;
2021-02-10 23:16:22 +01:00
XWTimerReason why;
XWTimerProc proc;
void* closure;
} TimerClosure;
2021-02-02 22:49:27 +01:00
2021-02-10 23:16:22 +01:00
static void
onTimerFired( void* closure )
2021-02-07 21:48:06 +01:00
{
2021-02-10 23:16:22 +01:00
LOG_FUNC();
TimerClosure* tc = (TimerClosure*)closure;
XP_Bool draw = (*tc->proc)( tc->closure, NULL, tc->why );
if ( draw ) {
updateScreen( tc->gs, true );
2021-02-10 23:16:22 +01:00
}
XP_FREE( tc->gs->globals->mpool, tc );
2021-02-07 21:48:06 +01:00
}
2021-02-02 22:49:27 +01:00
void
main_set_timer( GameState* gs, XWTimerReason why, XP_U16 when,
2021-02-02 22:49:27 +01:00
XWTimerProc proc, void* closure )
{
2021-02-10 23:16:22 +01:00
XP_LOGFF( "why: %d", why );
TimerClosure* tc = XP_MALLOC( gs->globals->mpool, sizeof(*tc) );
tc->gs = gs;
2021-02-10 23:16:22 +01:00
tc->proc = proc;
tc->closure = closure;
tc->why = why;
if ( 0 == when ) {
when = 1;
}
when *= 1000; /* convert to ms */
2021-02-02 22:49:27 +01:00
2021-02-10 23:16:22 +01:00
jscallback_set( onTimerFired, tc, when );
2021-02-02 22:49:27 +01:00
}
typedef struct _QueryState {
GameState* gs;
QueryProc proc;
void* closure;
} QueryState;
static void
onQueryCalled( void* closure, const char* button )
{
QueryState* qs = (QueryState*)closure;
bool ok = 0 == strcmp( button, BUTTON_OK );
(*qs->proc)( qs->closure, ok );
XP_FREE( qs->gs->globals->mpool, qs );
}
2021-02-02 23:52:25 +01:00
void
main_query( GameState* gs, const XP_UCHAR* query, QueryProc proc, void* closure )
2021-02-02 23:52:25 +01:00
{
if ( isVisible(gs) ) {
QueryState* qs = XP_MALLOC( gs->globals->mpool, sizeof(*qs) );
qs->proc = proc;
qs->closure = closure;
qs->gs = gs;
const char* buttons[] = { BUTTON_CANCEL, BUTTON_OK, NULL };
call_dialog( query, buttons, onQueryCalled, qs );
}
2021-02-02 23:52:25 +01:00
}
2021-02-03 04:20:26 +01:00
void
main_alert( GameState* gs, const XP_UCHAR* msg )
2021-02-03 04:20:26 +01:00
{
if ( isVisible(gs) ) {
call_alert( msg );
}
2021-02-03 04:20:26 +01:00
}
2021-02-10 22:21:28 +01:00
typedef struct _IdleClosure {
GameState* gs;
2021-02-10 22:21:28 +01:00
IdleProc proc;
void* closure;
} IdleClosure;
static void
onIdleFired( void* closure )
{
LOG_FUNC();
IdleClosure* ic = (IdleClosure*)closure;
XP_Bool draw = (*ic->proc)(ic->closure);
if ( draw ) {
updateScreen( ic->gs, true );
2021-02-10 22:21:28 +01:00
}
XP_FREE( ic->gs->globals->mpool, ic );
2021-02-10 22:21:28 +01:00
}
2021-02-03 04:20:26 +01:00
void
main_set_idle( GameState* gs, IdleProc proc, void* closure )
2021-02-03 04:20:26 +01:00
{
2021-02-10 22:21:28 +01:00
LOG_FUNC();
IdleClosure* ic = XP_MALLOC( gs->globals->mpool, sizeof(*ic) );
ic->gs = gs;
2021-02-10 22:21:28 +01:00
ic->proc = proc;
ic->closure = closure;
jscallback_set( onIdleFired, ic, 0 );
2021-02-03 04:20:26 +01:00
}
static XP_Bool
checkForEvent( GameState* gs )
{
XP_Bool handled;
XP_Bool draw = XP_FALSE;
BoardCtxt* board = gs->game.board;
SDL_Event event;
if ( SDL_PollEvent(&event) ) {
switch ( event.type ) {
case SDL_MOUSEBUTTONDOWN:
draw = event.button.button == SDL_BUTTON_LEFT
&& board_handlePenDown( board, NULL,
event.button.x, event.button.y,
&handled );
break;
case SDL_MOUSEBUTTONUP:
draw = event.button.button == SDL_BUTTON_LEFT
&& board_handlePenUp( board, NULL,
event.button.x, event.button.y );
break;
case SDL_MOUSEMOTION:
draw = board_handlePenMove( board, NULL,
event.motion.x, event.motion.y );
break;
default:
break;
}
}
// XP_LOGFF( "draw: %d", draw );
2021-02-03 04:20:26 +01:00
return draw;
}
static void
clearScreen( Globals* globals )
{
SDL_RenderClear( globals->renderer );
SDL_RenderPresent( globals->renderer );
}
2021-02-06 17:54:08 +01:00
static void
updateScreen( GameState* gs, bool doSave )
{
Globals* globals = gs->globals;
if ( gs == getCurGame(globals) ) {
SDL_RenderClear( globals->renderer );
if ( !!gs->game.board ) {
board_draw( gs->game.board, NULL );
wasm_draw_render( globals->draw, globals->renderer );
}
SDL_RenderPresent( globals->renderer );
} else {
XP_LOGFF( "not drawing %s; not visible", gs->gameName );
}
2021-02-19 04:20:20 +01:00
2021-02-24 02:05:13 +01:00
updateGameButtons( globals );
2021-02-06 17:54:08 +01:00
/* Let's save state here too, though likely too often */
2021-02-10 21:18:15 +01:00
if ( doSave ) {
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
globals->vtMgr );
game_saveToStream( &gs->game, NULL, &gs->gi,
stream, ++gs->saveToken );
2021-02-19 20:19:16 +01:00
GameSummary summary;
game_summarize( &gs->game, &gs->gi, &summary );
2021-03-05 18:03:32 +01:00
char gameIDStr[32];
formatGameID( gameIDStr, sizeof(gameIDStr), gs->gi.gameID );
const XP_UCHAR* keys[] = { KEY_GAMES, gameIDStr, KEY_GAME, NULL };
dutil_storeStream( globals->dutil, NULL, keys, stream );
2021-02-10 21:18:15 +01:00
stream_destroy( stream, NULL );
game_saveSucceeded( &gs->game, NULL, gs->saveToken );
keys[2] = KEY_SUMMARY;
dutil_storePtr( globals->dutil, NULL, keys, &summary, sizeof(summary) );
2021-02-10 21:18:15 +01:00
}
}
2021-02-20 02:49:20 +01:00
void
main_updateScreen( GameState* gs )
2021-02-20 02:49:20 +01:00
{
updateScreen( gs, true );
2021-02-20 02:49:20 +01:00
}
2021-02-03 04:20:26 +01:00
static void
looper( void* closure )
{
Globals* globals = (Globals*)closure;
GameState* gs = getCurGame( globals );
if ( !!gs ) {
if ( checkForEvent( gs ) ) {
updateScreen( gs, true );
}
}
2021-03-05 21:04:07 +01:00
wasm_dutil_syncIf( globals->dutil );
2021-03-05 18:46:21 +01:00
#ifdef MEM_DEBUG
if ( mpool_getStats( globals->mpool, &globals->mpstats ) ) {
show_pool(globals->mpstats.curBytes, globals->mpstats.maxBytes);
/* XP_LOGFF( "mempool: cur: %d; max: %d", globals->mpstats.curBytes, */
/* globals->mpstats.maxBytes ); */
}
#endif
}
static bool
inviteFromArgv( Globals* globals, NetLaunchInfo* nlip,
int argc, const char** argv )
{
LOG_FUNC();
CurGameInfo gi = {0};
CommsAddrRec addr = {0};
MQTTDevID mqttDevID = 0;
XP_U16 nPlayersH = 0;
XP_U16 forceChannel = 0;
const XP_UCHAR* gameName = NULL;
const XP_UCHAR* inviteID = NULL;
for ( int ii = 0; ii < argc; ++ii ) {
const char* argp = argv[ii];
char* param = strchr(argp, '=');
if ( !param ) { /* no '='? */
continue;
}
char arg[8];
int argLen = param - argp;
XP_MEMCPY( arg, argp, argLen );
arg[argLen] = '\0';
++param; /* skip the '=' */
if ( 0 == strcmp( "lang", arg ) ) {
gi.dictLang = atoi(param);
} else if ( 0 == strcmp( "np", arg ) ) {
gi.nPlayers = atoi(param);
} else if ( 0 == strcmp( "nh", arg ) ) {
nPlayersH = atoi(param);
} else if ( 0 == strcmp( "gid", arg ) ) {
gi.gameID = atoi(param);
} else if ( 0 == strcmp( "fc", arg ) ) {
gi.forceChannel = atoi(param);
} else if ( 0 == strcmp( "nm", arg ) ) {
gameName = param;
} else if ( 0 == strcmp( "id", arg ) ) {
inviteID = param;
} else if ( 0 == strcmp( "wl", arg ) ) {
replaceStringIfDifferent( globals->mpool, &gi.dictName, param );
} else if ( 0 == strcmp( "r2id", arg ) ) {
if ( strToMQTTCDevID( param, &addr.u.mqtt.devID ) ) {
addr_addType( &addr, COMMS_CONN_MQTT );
} else {
XP_LOGFF( "bad devid %s", param );
}
} else {
XP_LOGFF( "dropping arg %s, param %s", arg, param );
}
}
bool success = 0 < nPlayersH &&
addr_hasType( &addr, COMMS_CONN_MQTT );
if ( success ) {
nli_init( nlip, &gi, &addr, nPlayersH, forceChannel );
if ( !!gameName ) {
nli_setGameName( nlip, gameName );
}
if ( !!inviteID ) {
nli_setInviteID( nlip, inviteID );
}
LOGNLI( nlip );
}
gi_disposePlayerInfo( MPPARM(globals->mpool) &gi );
LOG_RETURNF( "%d", success );
return success;
}
2021-02-15 23:59:44 +01:00
void
MQTTConnectedChanged( void* closure, bool connected )
{
if ( connected ) {
CAST_GLOB(Globals*, globals, closure);
GameState* gs = getCurGame( globals );
if ( !!gs && !!gs->game.comms ) {
comms_resendAll( gs->game.comms, NULL, COMMS_CONN_MQTT, XP_TRUE );
}
2021-02-15 23:59:44 +01:00
}
}
2021-02-07 21:48:06 +01:00
void
cbckBinary( BinProc proc, void* closure, int len, const uint8_t* msg )
2021-02-07 21:48:06 +01:00
{
(*proc)(closure, msg, len );
2021-02-07 21:48:06 +01:00
}
2021-03-10 05:13:42 +01:00
static void
onAllowNotify(void* closure, bool confirmed)
{
NewGameParams* ngp = (NewGameParams*)closure;
Globals* globals = ngp->globals;
if ( confirmed ) {
js_requestNotify();
}
loadAndDraw( globals, NULL, 0, ngp );
updateDeviceButtons( globals );
XP_FREE( globals->mpool, ngp );
}
2021-02-10 22:21:28 +01:00
void
onNewGame( void* closure, bool opponentIsRobot, const char* langName )
2021-02-10 22:21:28 +01:00
{
2021-02-21 19:07:51 +01:00
Globals* globals = (Globals*)closure;
XP_LOGFF( "isRobot: %d; lc: %s", opponentIsRobot, langName );
2021-03-10 05:13:42 +01:00
NewGameParams* ngp = XP_CALLOC( globals->mpool, sizeof(*ngp) );
ngp->globals = globals;
ngp->isRobotNotRemote = opponentIsRobot;
langNameToLC(globals, langName, ngp->lc, sizeof(ngp->lc));
2021-02-21 19:07:51 +01:00
2021-03-10 05:13:42 +01:00
if ( !opponentIsRobot && !js_getHaveNotifyPerm() ) {
const char* msg = "You are creating a networked game. Would you like "
"notifications when a move arrives and it becomes your turn?";
call_confirm( globals, msg, onAllowNotify, ngp );
} else {
onAllowNotify(ngp, false);
}
2021-02-10 22:21:28 +01:00
}
2021-02-21 19:07:51 +01:00
/* Called from js with a proc and closure */
void
2021-02-21 19:07:51 +01:00
cbckVoid( JSCallback proc, void* closure )
{
2021-02-21 19:07:51 +01:00
LOG_FUNC();
(*proc)(closure);
}
2021-02-21 19:07:51 +01:00
/* Called from js with a proc, closure, and string */
void
2021-02-21 19:07:51 +01:00
cbckString( StringProc proc, void* closure, const char* str )
{
2021-02-21 19:07:51 +01:00
if ( !!proc ) {
(*proc)( closure, str );
}
}
void
gotDictBinary( GotDictProc proc, void* closure, const char* xwd,
2021-03-08 16:39:47 +01:00
const char* lc, const char* langName,
uint8_t* data, int len )
{
2021-03-08 16:39:47 +01:00
XP_LOGFF( "lc: %s, langName: %s, xwd: %s; len: %d",
lc, langName, xwd, len );
GotDictData gdd = { .lc = lc,
.dictName = xwd,
.langName = langName,
.data = data,
.len = len,
};
(*proc)(closure, &gdd);
}
/* On first launch, we may have an invitation. Or not. We want to ask for a
* local name (but only once), then download any wordlist we need, then open
* any pre-existing game or else a new one.
*/
typedef struct _LaunchState {
Globals* globals;
NetLaunchInfo invite;
char playerName[32];
bool hadName;
} LaunchState;
static void
2021-03-08 21:46:42 +01:00
storeOrAlert( Globals* globals, GotDictData* gdd )
{
2021-03-08 21:46:42 +01:00
if ( !!gdd ) {
if ( 0 == gdd->len || !storeAsDict( globals, gdd ) ) {
call_alert( "Unable to download wordlist. Reload the page to try again?" );
}
}
2021-03-08 21:46:42 +01:00
}
static void
onGotInviteDictAtLaunch( void* closure, GotDictData* gdd )
{
// XP_LOGFF("(gdd: %p)", gdd );
LaunchState* ls = (LaunchState*)closure;
Globals* globals = ls->globals;
storeOrAlert( globals, gdd );
/* We're ready to start. If we had an invitation, launch for it. Otherwise
launch the last game that was open */
NetLaunchInfo* nlip = ls->invite.lang == 0 ? NULL : &ls->invite;
int gameID = 0;
XP_U32 len = sizeof(gameID);
const XP_UCHAR* keys[] = {KEY_LAST_GID, NULL};
dutil_loadPtr( globals->dutil, NULL, keys, (XP_U8*)&gameID, &len );
XP_LOGFF( "loaded KEY_LAST_GID: %X", gameID );
loadAndDraw( globals, nlip, gameID, NULL );
updateDeviceButtons( globals );
XP_FREE( globals->mpool, ls );
}
2021-03-08 21:46:42 +01:00
static void
onGotNativeDictAtLaunch( void* closure, GotDictData* gdd )
{
LaunchState* ls = (LaunchState*)closure;
Globals* globals = ls->globals;
// XP_LOGFF("(gdd: %p)", gdd );
storeOrAlert( globals, gdd );
/* Now download a wordlist if we need one */
const char* neededLC = NULL;
if ( 0 != ls->invite.lang ) { /* 0 means unset: no invite */
const char* lc = lcToLocale( ls->invite.lang );
if ( !haveDictFor(ls->globals, lc) ) {
neededLC = lc;
}
}
if ( !!neededLC ) {
call_get_dict( neededLC, onGotInviteDictAtLaunch, ls );
} else {
onGotInviteDictAtLaunch( ls, NULL);
}
}
static void
onPlayerNamedAtLaunch( void* closure, const char* responseName )
{
LaunchState* ls = (LaunchState*)closure;
2021-03-08 21:46:42 +01:00
Globals* globals = ls->globals;
// XP_LOGFF("(name: %s)", responseName );
/* Did user change name? Save it */
if ( !!responseName && 0 != strcmp( responseName, ls->playerName ) ) {
2021-03-08 21:46:42 +01:00
onPlayerNamed( globals, responseName );
} else if ( !ls->hadName ) {
2021-03-08 21:46:42 +01:00
onPlayerNamed( globals, ls->playerName );
char buf[128];
sprintf( buf, "Ok. Using default name %s. You can change it anytime "
"using the \"%s\" button.", ls->playerName, BUTTON_NAME );
call_alert( buf );
}
2021-03-08 21:46:42 +01:00
if ( 0 == countDicts( globals ) ) {
call_get_dict( NULL, onGotNativeDictAtLaunch, ls );
} else {
2021-03-08 21:46:42 +01:00
onGotNativeDictAtLaunch( ls, NULL );
}
}
static void
startLaunchSequence( Globals* globals, NetLaunchInfo* nli )
{
LaunchState* ls = XP_CALLOC( globals->mpool, sizeof(*ls) );
ls->globals = globals;
if ( NULL != nli ) {
ls->invite = *nli;
}
/* No saved name? Ask. Politely */
if ( getLocalName( globals, ls->playerName, sizeof(ls->playerName) ) ) {
ls->hadName = true;
onPlayerNamedAtLaunch( ls, NULL );
} else {
call_get_string( "Please choose a name for your player. It's what you "
"and others will see in the scoreboard", ls->playerName,
onPlayerNamedAtLaunch, ls );
}
}
2021-03-05 18:03:32 +01:00
void
mainPostSync( int argc, const char** argv )
{
XP_LOGFF( "(argc=%d)", argc );
time_t now = getCurMS();
srandom( now );
XP_LOGFF( "called(srandom( %x )", now );
#ifdef GLOBALS_ON_STACK
Globals _globals = {0};
Globals* globals = &_globals;
#else
Globals* globals = calloc(1, sizeof(*globals));
#endif
#ifdef DEBUG
globals->_GUARD = GUARD_GLOB;
#endif
NetLaunchInfo nli = {0};
NetLaunchInfo* nlip = NULL;
if ( inviteFromArgv( globals, &nli, argc, argv ) ) {
nlip = &nli;
}
SDL_Init( SDL_INIT_EVENTS );
TTF_Init();
SDL_CreateWindowAndRenderer( WINDOW_WIDTH, WINDOW_HEIGHT, 0,
&globals->window, &globals->renderer );
/* wipe the canvas to background */
SDL_SetRenderDrawColor( globals->renderer, 155, 155, 155, 255 );
SDL_RenderClear( globals->renderer );
initDeviceGlobals( globals ); /* takes care of getting mqtt devid */
startLaunchSequence( globals, nlip );
#ifdef GLOBALS_ON_STACK
emscripten_set_main_loop_arg( looper, globals, -1, 1 );
#else
emscripten_set_main_loop_arg( looper, globals, -1, 0 );
#endif
2021-03-05 18:03:32 +01:00
}
EM_JS( void, loadDBThen, (const char* root, int argc, const char** argv), {
let jsroot = UTF8ToString(root);
FS.mkdir(jsroot);
FS.mount(IDBFS, {}, jsroot);
FS.syncfs(true, function (err) {
if ( err ) {
console.log('fsSyncIn: err: ' + err);
}
ccall('mainPostSync', null, ['number', 'number'], [argc, argv]);
});
});
int
main( int argc, const char** argv )
{
XP_LOGFF( "MAIN ENTRY(argc=%d)", argc );
2021-03-05 18:03:32 +01:00
loadDBThen( ROOT_PATH, argc, argv );
2021-02-01 17:30:34 +01:00
return 0;
}