mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-07 05:24:46 +01:00
5a061f5786
I'm wrestling with how to have concurrent tabs open. They can't be on the same game because both can't connect using same MQTT dev id (and they'd quickly get the saved state screwed up.) Only problem is sometimes a tab in a new window doesn't have access to localstorage and so is able to connect on its own. But later that state isn't reachable any more: relaunch firefox and you get back the to the older state.
786 lines
23 KiB
C
786 lines
23 KiB
C
/* -*- compile-command: "cd ../wasm && make main.html -j3"; -*- */
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/time.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_ttf.h>
|
|
#include <emscripten.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "game.h"
|
|
#include "device.h"
|
|
#include "mempool.h"
|
|
#include "nli.h"
|
|
#include "strutils.h"
|
|
#include "movestak.h"
|
|
|
|
#include "main.h"
|
|
#include "wasmdraw.h"
|
|
#include "wasmutil.h"
|
|
#include "wasmdutil.h"
|
|
#include "wasmdict.h"
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten.h>
|
|
#endif
|
|
|
|
#define WASM_BOARD_LEFT 0
|
|
#define WASM_HOR_SCORE_TOP 0
|
|
|
|
#define WINDOW_WIDTH 400
|
|
#define WINDOW_HEIGHT 600
|
|
#define BDWIDTH WINDOW_WIDTH
|
|
#define BDHEIGHT WINDOW_HEIGHT
|
|
|
|
#define KEY_GAME "the_game"
|
|
#define DICTNAME "assets_dir/CollegeEng_2to8.xwd"
|
|
|
|
#define BUTTON_OK "OK"
|
|
#define BUTTON_CANCEL "Cancel"
|
|
|
|
typedef void (*AlertProc)(void* closure, const char* button);
|
|
|
|
/* typedef struct _Buttons { */
|
|
/* int nButtons; */
|
|
/* const char** buttons; */
|
|
/* } Buttons; */
|
|
|
|
EM_JS(void, call_dialog, (const char* str, const char** but_strs,
|
|
AlertProc proc, void* closure), {
|
|
var buttons = [];
|
|
for ( let ii = 0; ii < 3; ++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);
|
|
} );
|
|
|
|
EM_JS(void, call_haveDevID, (void* closure, const char* devid), {
|
|
onHaveDevID(closure, UTF8ToString(devid));
|
|
});
|
|
|
|
EM_JS(bool, call_mqttSend, (const char* topic, const uint8_t* ptr, int len), {
|
|
let topStr = UTF8ToString(topic);
|
|
let buffer = new Uint8Array(Module.HEAPU8.buffer, ptr, len);
|
|
return mqttSend(topStr, buffer);
|
|
});
|
|
|
|
typedef void (*JSCallback)(void* closure);
|
|
|
|
EM_JS(void, jscallback_set, (JSCallback proc, void* closure, int inMS), {
|
|
let timerproc = function(closure) {
|
|
ccall('jscallback', null, ['number', 'number'], [proc, closure]);
|
|
};
|
|
setTimeout( timerproc, inMS, closure );
|
|
});
|
|
|
|
EM_JS(void, setButtonText, (const char* id, const char* text), {
|
|
let jsid = UTF8ToString(id);
|
|
let jstext = UTF8ToString(text);
|
|
document.getElementById(jsid).textContent = jstext;
|
|
});
|
|
|
|
static void updateScreen( Globals* globals, bool doSave );
|
|
|
|
static void
|
|
call_alert( const char* msg )
|
|
{
|
|
const char* buttons[] = { BUTTON_OK, NULL };
|
|
call_dialog( msg, buttons, NULL, NULL );
|
|
}
|
|
|
|
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;
|
|
LOG_FUNC();
|
|
Globals* globals = (Globals*)closure;
|
|
|
|
if ( addr_hasType( addr, COMMS_CONN_MQTT ) ) {
|
|
MQTTDevID devID = addr->u.mqtt.devID;
|
|
|
|
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
|
|
globals->vtMgr );
|
|
dvc_makeMQTTMessage( globals->dutil, NULL, stream,
|
|
gameID, buf, len );
|
|
|
|
XP_UCHAR topic[64];
|
|
formatMQTTTopic( &devID, topic, sizeof(topic) );
|
|
|
|
XP_U16 streamLen = stream_getSize( stream );
|
|
|
|
XP_LOGFF( "calling call_mqttSend" );
|
|
if ( call_mqttSend( topic, stream_getPtr( stream ), streamLen ) ) {
|
|
nSent = len;
|
|
}
|
|
XP_LOGFF( "back from call_mqttSend" );
|
|
stream_destroy( stream, NULL );
|
|
}
|
|
|
|
LOG_RETURNF( "%d", nSent );
|
|
return nSent;
|
|
}
|
|
|
|
static void
|
|
updateTradeButton( Globals* globals )
|
|
{
|
|
XP_Bool inTrade = board_inTrade( globals->game.board, NULL );
|
|
const char* text = inTrade ? "Cancel trade" : "Trade";
|
|
setButtonText( "trade", text );
|
|
}
|
|
|
|
static void
|
|
initDeviceGlobals( Globals* globals )
|
|
{
|
|
globals->cp.showBoardArrow = XP_TRUE;
|
|
globals->cp.allowPeek = XP_TRUE;
|
|
// globals->cp.showRobotScores = XP_TRUE;
|
|
globals->cp.sortNewTiles = XP_TRUE;
|
|
globals->cp.showColors = XP_TRUE;
|
|
|
|
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->dict = wasm_dictionary_make( MPPARM(globals->mpool) NULL,
|
|
globals, DICTNAME, true );
|
|
|
|
dict_ref( globals->dict, NULL );
|
|
|
|
globals->draw = wasm_draw_make( MPPARM(globals->mpool)
|
|
WINDOW_WIDTH, WINDOW_HEIGHT );
|
|
|
|
MQTTDevID devID;
|
|
dvc_getMQTTDevID( globals->dutil, NULL, &devID );
|
|
XP_UCHAR buf[32];
|
|
XP_SNPRINTF( buf, VSIZE(buf), MQTTDevID_FMT, devID );
|
|
XP_LOGFF( "got mqtt devID: %s", buf );
|
|
call_haveDevID( globals, buf );
|
|
}
|
|
|
|
static void
|
|
startGame( Globals* globals )
|
|
{
|
|
LOG_FUNC();
|
|
BoardDims dims;
|
|
board_figureLayout( globals->game.board, NULL, &globals->gi,
|
|
WASM_BOARD_LEFT, WASM_HOR_SCORE_TOP, BDWIDTH, BDHEIGHT,
|
|
110, 150, 200, BDWIDTH-25, BDWIDTH/15, BDHEIGHT/15,
|
|
XP_FALSE, &dims );
|
|
XP_LOGFF( "calling board_applyLayout" );
|
|
board_applyLayout( globals->game.board, NULL, &dims );
|
|
XP_LOGFF( "calling model_setDictionary" );
|
|
model_setDictionary( globals->game.model, NULL, globals->dict );
|
|
|
|
if ( SERVER_ISCLIENT == globals->gi.serverRole ) {
|
|
server_initClientConnection( globals->game.server, NULL );
|
|
}
|
|
|
|
(void)server_do( globals->game.server, NULL ); /* assign tiles, etc. */
|
|
if ( !!globals->game.comms ) {
|
|
comms_resendAll( globals->game.comms, NULL, COMMS_CONN_MQTT, XP_TRUE );
|
|
}
|
|
|
|
updateScreen( globals, true );
|
|
LOG_RETURN_VOID();
|
|
}
|
|
|
|
static bool
|
|
gameFromInvite( Globals* globals, const NetLaunchInfo* invite )
|
|
{
|
|
bool loaded = false;
|
|
bool needsLoad = true;
|
|
XP_LOGFF( "model: %p", globals->game.model );
|
|
if ( NULL != globals->game.model ) {
|
|
XP_LOGFF( "have game: TRUE" );
|
|
/* there's a current game. Ignore the invitation if it has the same
|
|
gameID. Otherwise ask to replace */
|
|
if ( globals->gi.gameID == invite->gameID ) {
|
|
XP_LOGFF( "duplicate invite; ignoring" );
|
|
needsLoad = false;
|
|
} else {
|
|
call_alert( "Invitation received; replace current game?" );
|
|
// needsLoad = false;
|
|
}
|
|
} else if ( invite->lang != 1 ) {
|
|
call_alert( "Invitations are only supported for play in English right now." );
|
|
needsLoad = false;
|
|
}
|
|
|
|
if ( needsLoad ) {
|
|
if ( !!globals->util ) {
|
|
game_dispose( &globals->game, NULL );
|
|
wasm_util_destroy( globals->util );
|
|
globals->util = NULL;
|
|
}
|
|
|
|
gi_disposePlayerInfo( MPPARM(globals->mpool) &globals->gi );
|
|
XP_MEMSET( &globals->gi, 0, sizeof(globals->gi) );
|
|
|
|
globals->util = wasm_util_make( MPPARM(globals->mpool) &globals->gi,
|
|
globals->dutil, globals );
|
|
|
|
loaded = game_makeFromInvite( MPPARM(globals->mpool) NULL, invite,
|
|
&globals->game, &globals->gi,
|
|
globals->dict, NULL,
|
|
globals->util, globals->draw,
|
|
&globals->cp, &globals->procs );
|
|
|
|
} else {
|
|
loaded = true;
|
|
}
|
|
LOG_RETURNF( "%d", loaded );
|
|
return loaded;
|
|
}
|
|
|
|
static bool
|
|
loadSavedGame( Globals* globals )
|
|
{
|
|
bool loaded = false;
|
|
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
|
|
globals->vtMgr );
|
|
dutil_loadStream( globals->dutil, NULL, KEY_GAME, NULL, stream );
|
|
if ( 0 < stream_getSize( stream ) ) {
|
|
XP_ASSERT( !globals->util );
|
|
globals->util = wasm_util_make( MPPARM(globals->mpool) &globals->gi,
|
|
globals->dutil, globals );
|
|
|
|
XP_LOGFF( "there's a saved game!!" );
|
|
loaded = game_makeFromStream( MPPARM(globals->mpool) NULL, stream,
|
|
&globals->game, &globals->gi,
|
|
globals->dict, NULL,
|
|
globals->util, globals->draw,
|
|
&globals->cp, &globals->procs );
|
|
|
|
if ( loaded ) {
|
|
updateScreen( globals, false );
|
|
}
|
|
updateTradeButton( globals );
|
|
}
|
|
stream_destroy( stream, NULL );
|
|
return loaded;
|
|
}
|
|
|
|
static void
|
|
loadAndDraw( Globals* globals, const NetLaunchInfo* invite,
|
|
bool forceNew, bool p0robot, bool p1robot )
|
|
{
|
|
if ( !!globals->util ) {
|
|
game_dispose( &globals->game, NULL );
|
|
wasm_util_destroy( globals->util );
|
|
globals->util = NULL;
|
|
}
|
|
|
|
bool haveGame;
|
|
if ( forceNew ) {
|
|
haveGame = false;
|
|
} else {
|
|
/* First, load any saved game. We need it e.g. to confirm that an incoming
|
|
invite is a dup and should be dropped. */
|
|
haveGame = loadSavedGame( globals );
|
|
|
|
if ( !!invite ) {
|
|
haveGame = gameFromInvite( globals, invite );
|
|
}
|
|
}
|
|
|
|
if ( !haveGame ) {
|
|
globals->gi.serverRole = SERVER_STANDALONE;
|
|
globals->gi.phoniesAction = PHONIES_WARN;
|
|
globals->gi.gameID = 0;
|
|
globals->gi.nPlayers = 2;
|
|
globals->gi.boardSize = 15;
|
|
globals->gi.players[0].name = copyString( globals->mpool, "Player 1" );
|
|
globals->gi.players[0].isLocal = XP_TRUE;
|
|
globals->gi.players[0].robotIQ = p0robot ? 99 : 0;
|
|
|
|
globals->gi.players[1].name = copyString( globals->mpool, "Player 2" );
|
|
globals->gi.players[1].isLocal = XP_TRUE;
|
|
globals->gi.players[1].robotIQ = p1robot ? 99 : 0;
|
|
|
|
globals->util = wasm_util_make( MPPARM(globals->mpool) &globals->gi,
|
|
globals->dutil, globals );
|
|
|
|
XP_LOGFF( "calling game_makeNewGame()" );
|
|
game_makeNewGame( MPPARM(globals->mpool) NULL,
|
|
&globals->game, &globals->gi,
|
|
globals->util, globals->draw,
|
|
&globals->cp, &globals->procs );
|
|
}
|
|
|
|
startGame( globals );
|
|
}
|
|
|
|
void
|
|
main_gameFromInvite( Globals* globals, const NetLaunchInfo* invite )
|
|
{
|
|
if ( gameFromInvite( globals, invite ) ) {
|
|
startGame( globals );
|
|
}
|
|
}
|
|
|
|
void
|
|
main_onGameMessage( Globals* globals, XP_U32 gameID,
|
|
const CommsAddrRec* from, XWStreamCtxt* stream )
|
|
{
|
|
XP_Bool draw = game_receiveMessage( &globals->game, NULL, stream, from );
|
|
if ( draw ) {
|
|
updateScreen( globals, true );
|
|
}
|
|
}
|
|
|
|
void
|
|
main_sendOnClose( XWStreamCtxt* stream, XWEnv env, void* closure )
|
|
{
|
|
Globals* globals = (Globals*)closure;
|
|
XP_LOGFF( "called with msg of len %d", stream_getSize(stream) );
|
|
(void)comms_send( globals->game.comms, NULL, stream );
|
|
}
|
|
|
|
void
|
|
main_playerScoreHeld( Globals* globals, XP_U16 player )
|
|
{
|
|
LastMoveInfo lmi;
|
|
XP_UCHAR buf[128];
|
|
if ( model_getPlayersLastScore( globals->game.model, NULL, player, &lmi ) ) {
|
|
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 );
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
main_clear_timer( Globals* globals, XWTimerReason why )
|
|
{
|
|
XP_LOGFF( "why: %d" );
|
|
// XP_ASSERT(0); fires when start new game
|
|
}
|
|
|
|
typedef struct _TimerClosure {
|
|
Globals* globals;
|
|
XWTimerReason why;
|
|
XWTimerProc proc;
|
|
void* closure;
|
|
} TimerClosure;
|
|
|
|
static void
|
|
onTimerFired( void* closure )
|
|
{
|
|
LOG_FUNC();
|
|
TimerClosure* tc = (TimerClosure*)closure;
|
|
XP_Bool draw = (*tc->proc)( tc->closure, NULL, tc->why );
|
|
if ( draw ) {
|
|
updateScreen( tc->globals, true );
|
|
}
|
|
XP_FREE( tc->globals->mpool, tc );
|
|
}
|
|
|
|
void
|
|
main_set_timer( Globals* globals, XWTimerReason why, XP_U16 when,
|
|
XWTimerProc proc, void* closure )
|
|
{
|
|
XP_LOGFF( "why: %d", why );
|
|
TimerClosure* tc = XP_MALLOC( globals->mpool, sizeof(*tc) );
|
|
tc->globals = globals;
|
|
tc->proc = proc;
|
|
tc->closure = closure;
|
|
tc->why = why;
|
|
|
|
if ( 0 == when ) {
|
|
when = 1;
|
|
}
|
|
when *= 1000; /* convert to ms */
|
|
|
|
jscallback_set( onTimerFired, tc, when );
|
|
}
|
|
|
|
typedef struct _QueryState {
|
|
Globals* globals;
|
|
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 );
|
|
updateTradeButton( qs->globals );
|
|
XP_FREE( qs->globals->mpool, qs );
|
|
}
|
|
|
|
void
|
|
main_query( Globals* globals, const XP_UCHAR* query, QueryProc proc, void* closure )
|
|
{
|
|
QueryState* qs = XP_MALLOC( globals->mpool, sizeof(*qs) );
|
|
qs->proc = proc;
|
|
qs->closure = closure;
|
|
qs->globals = globals;
|
|
|
|
const char* buttons[] = { BUTTON_CANCEL, BUTTON_OK, NULL };
|
|
call_dialog( query, buttons, onQueryCalled, qs );
|
|
}
|
|
|
|
void
|
|
main_alert( Globals* globals, const XP_UCHAR* msg )
|
|
{
|
|
call_alert( msg );
|
|
}
|
|
|
|
typedef struct _IdleClosure {
|
|
Globals* globals;
|
|
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->globals, true );
|
|
}
|
|
XP_FREE( ic->globals->mpool, ic );
|
|
}
|
|
|
|
void
|
|
main_set_idle( Globals* globals, IdleProc proc, void* closure )
|
|
{
|
|
LOG_FUNC();
|
|
IdleClosure* ic = XP_MALLOC( globals->mpool, sizeof(*ic) );
|
|
ic->globals = globals;
|
|
ic->proc = proc;
|
|
ic->closure = closure;
|
|
|
|
jscallback_set( onIdleFired, ic, 0 );
|
|
}
|
|
|
|
static XP_Bool
|
|
checkForEvent( Globals* globals )
|
|
{
|
|
XP_Bool handled;
|
|
XP_Bool draw = XP_FALSE;
|
|
BoardCtxt* board = globals->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 );
|
|
return draw;
|
|
}
|
|
|
|
static void
|
|
updateScreen( Globals* globals, bool doSave )
|
|
{
|
|
SDL_RenderClear( globals->renderer );
|
|
board_draw( globals->game.board, NULL );
|
|
wasm_draw_render( globals->draw, globals->renderer );
|
|
SDL_RenderPresent( globals->renderer );
|
|
|
|
/* Let's save state here too, though likely too often */
|
|
if ( doSave ) {
|
|
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool)
|
|
globals->vtMgr );
|
|
game_saveToStream( &globals->game, NULL, &globals->gi,
|
|
stream, ++globals->saveToken );
|
|
dutil_storeStream( globals->dutil, NULL, KEY_GAME, stream );
|
|
stream_destroy( stream, NULL );
|
|
game_saveSucceeded( &globals->game, NULL, globals->saveToken );
|
|
}
|
|
}
|
|
|
|
static void
|
|
doExit( Globals* globals )
|
|
{
|
|
call_alert( "Control passed to another tab" );
|
|
XP_MEMSET( globals, 0, sizeof(*globals) ); /* stop everything :-) */
|
|
// emscripten_cancel_main_loop(); <-- does nothing
|
|
}
|
|
|
|
static void
|
|
looper( void* closure )
|
|
{
|
|
Globals* globals = (Globals*)closure;
|
|
if ( checkForEvent( globals ) ) {
|
|
updateScreen( globals, true );
|
|
}
|
|
}
|
|
|
|
void
|
|
button( void* closure, const char* msg )
|
|
{
|
|
XP_Bool draw = XP_FALSE;
|
|
Globals* globals = (Globals*)closure;
|
|
BoardCtxt* board = globals->game.board;
|
|
XP_Bool redo;
|
|
|
|
if ( 0 == strcmp(msg, "hintdown") ) {
|
|
draw = board_requestHint( board, NULL, XP_TRUE, &redo );
|
|
} else if ( 0 == strcmp(msg, "hintup") ) {
|
|
draw = board_requestHint( board, NULL, XP_FALSE, &redo );
|
|
} else if ( 0 == strcmp(msg, "trade") ) {
|
|
draw = board_inTrade( board, NULL )
|
|
? board_endTrade( board )
|
|
: board_beginTrade( board, NULL );
|
|
updateTradeButton( globals );
|
|
} else if ( 0 == strcmp(msg, "commit") ) {
|
|
draw = board_commitTurn( board, NULL, XP_FALSE, XP_FALSE, NULL );
|
|
} else if ( 0 == strcmp(msg, "flip") ) {
|
|
draw = board_flip( board );
|
|
} else if ( 0 == strcmp(msg, "redo") ) {
|
|
draw = board_redoReplacedTiles( board, NULL )
|
|
|| board_replaceTiles( board, NULL );
|
|
} else if ( 0 == strcmp(msg, "vals") ) {
|
|
globals->cp.tvType = (globals->cp.tvType + 1) % TVT_N_ENTRIES;
|
|
draw = board_prefsChanged( board, &globals->cp );
|
|
} else if ( 0 == strcmp(msg, "exit") ) {
|
|
doExit( globals );
|
|
}
|
|
|
|
if ( draw ) {
|
|
updateScreen( globals, true );
|
|
}
|
|
}
|
|
|
|
static bool
|
|
loadInvite( 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;
|
|
}
|
|
|
|
static void
|
|
initNoReturn( int argc, const char** argv )
|
|
{
|
|
time_t now = getCurMS();
|
|
srandom( now );
|
|
XP_LOGFF( "called(srandom( %x )", now );
|
|
|
|
Globals* globals = calloc(1, sizeof(*globals));
|
|
|
|
NetLaunchInfo nli = {0};
|
|
NetLaunchInfo* nlip = NULL;
|
|
if ( loadInvite( globals, &nli, argc, argv ) ) {
|
|
nlip = &nli;
|
|
}
|
|
|
|
SDL_Init( SDL_INIT_EVENTS );
|
|
TTF_Init();
|
|
|
|
SDL_CreateWindowAndRenderer( WINDOW_WIDTH, WINDOW_HEIGHT, 0,
|
|
&globals->window, &globals->renderer );
|
|
|
|
/* whip the canvas to background */
|
|
SDL_SetRenderDrawColor( globals->renderer, 155, 155, 155, 255 );
|
|
SDL_RenderClear( globals->renderer );
|
|
|
|
initDeviceGlobals( globals );
|
|
|
|
loadAndDraw( globals, nlip, false, false, true );
|
|
|
|
emscripten_set_main_loop_arg( looper, globals, -1, 1 );
|
|
}
|
|
|
|
typedef struct _NewgameState {
|
|
Globals* globals;
|
|
bool p0;
|
|
bool p1;
|
|
} NewgameState;
|
|
|
|
static void
|
|
onNewgameResponse( void* closure, const char* button )
|
|
{
|
|
NewgameState* ngs = (NewgameState*)closure;
|
|
Globals* globals = ngs->globals;
|
|
if ( 0 == strcmp( button, BUTTON_OK ) ) {
|
|
loadAndDraw( ngs->globals, NULL, true, ngs->p0, ngs->p1 );
|
|
}
|
|
XP_FREE( globals->mpool, ngs );
|
|
}
|
|
|
|
void
|
|
newgame( void* closure, bool p0, bool p1 )
|
|
{
|
|
Globals* globals = (Globals*)closure;
|
|
NewgameState* ngs = XP_MALLOC( globals->mpool, sizeof(*ngs) );
|
|
ngs->globals = globals;
|
|
ngs->p0 = p0;
|
|
ngs->p1 = p1;
|
|
XP_LOGFF( "(args: %d,%d)", p0, p1 );
|
|
const char* query = "Are you sure you want to replace the current game?";
|
|
const char* buttons[] = { BUTTON_CANCEL, BUTTON_OK, NULL };
|
|
call_dialog( query, buttons, onNewgameResponse, ngs );
|
|
}
|
|
|
|
void
|
|
gotMQTTMsg( void* closure, int len, const uint8_t* msg )
|
|
{
|
|
Globals* globals = (Globals*)closure;
|
|
dvc_parseMQTTPacket( globals->dutil, NULL, msg, len );
|
|
}
|
|
|
|
void
|
|
jscallback( JSCallback proc, void* closure )
|
|
{
|
|
LOG_FUNC();
|
|
(*proc)(closure);
|
|
}
|
|
|
|
void
|
|
onDlgButton( AlertProc proc, void* closure, const char* button )
|
|
{
|
|
if ( !!proc ) {
|
|
(*proc)( closure, button );
|
|
}
|
|
}
|
|
|
|
int
|
|
main( int argc, const char** argv )
|
|
{
|
|
XP_LOGFF( "(argc=%d)", argc );
|
|
initNoReturn( argc, argv );
|
|
return 0;
|
|
}
|