xwords/xwords4/linux/cursesboard.c
Eric House 6018392160 send/handle all invitations at once
I'm trying to reproduce with initial invitations the stalls I'm seeing
with rematches (rarely)
2024-01-04 20:28:49 -08:00

1747 lines
55 KiB
C

/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */
/*
* Copyright 2000 - 2020 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <ncurses.h>
#include <wchar.h>
#include "cursesboard.h"
#include "linuxmain.h"
#include "linuxutl.h"
#include "relaycon.h"
#include "mqttcon.h"
#include "cursesask.h"
#include "cursesmenu.h"
#include "cursesletterask.h"
#include "linuxdict.h"
#include "gamesdb.h"
#include "game.h"
#include "gsrcwrap.h"
#include "linuxsms.h"
#include "strutils.h"
#include "dbgutil.h"
typedef struct CursesBoardState {
CursesAppGlobals* aGlobals;
LaunchParams* params;
CursesMenuState* menuState;
OnGameSaved onGameSaved;
GSList* games;
} CursesBoardState;
struct CursesBoardGlobals {
CommonGlobals cGlobals;
CursesBoardState* cbState;
CursesMenuState* menuState; /* null if we're not using menus */
int refCount;
union {
struct {
XWStreamCtxt* stream; /* how we can reach the server */
} client;
struct {
int serverSocket;
XP_Bool socketOpen;
} server;
} csInfo;
XWGameState state;
XP_U16 nChatsSent;
XP_U16 nextQueryTimeSecs;
WINDOW* boardWin;
int winWidth, winHeight;
const MenuList* lastSubMenu;
XP_Bool amServer; /* this process acting as server */
};
static CursesBoardGlobals* findOrOpen( CursesBoardState* cbState,
sqlite3_int64 rowid,
const CurGameInfo* gi,
const CommsAddrRec* addrP );
static void enableDraw( CursesBoardGlobals* bGlobals, const cb_dims* dims );
static CursesBoardGlobals* ref( CursesBoardGlobals* bGlobals );
static void unref( CursesBoardGlobals* bGlobals );
static void setupBoard( CursesBoardGlobals* bGlobals );
static void initMenus( CursesBoardGlobals* bGlobals );
static void disposeDraw( CursesBoardGlobals* bGlobals );
#ifdef KEYBOARD_NAV
static bool handleLeft( void* closure, int key );
static bool handleRight( void* closure, int key );
static bool handleUp( void* closure, int key );
static bool handleDown( void* closure, int key );
static bool handleClose( void* closure, int key );
#endif
#ifdef ALT_KEYS_WORKING
static bool handleAltLeft( void* closure, int key );
static bool handleAltRight( void* closure, int key );
static bool handleAltUp( void* closure, int key );
static bool handleAltDown( void* closure, int key );
#endif
static bool handleJuggle( void* closure, int key );
static bool handleHide( void* closure, int key );
/* static bool handleResend( void* closure, int key ); */
static bool handleSpace( void* closure, int key );
static bool handleRet( void* closure, int key );
static bool handleShowVals( void* closure, int key );
static bool handleHint( void* closure, int key );
static bool handleCommit( void* closure, int key );
static bool handleFlip( void* closure, int key );
static bool handleToggleValues( void* closure, int key );
static bool handleBackspace( void* closure, int key );
static bool handleUndo( void* closure, int key );
static bool handleReplace( void* closure, int key );
#ifdef CURSES_SMALL_SCREEN
static bool handleRootKeyShow( void* closure, int key );
static bool handleRootKeyHide( void* closure, int key );
#endif
static bool sendInvite( void* closure, int key );
#ifdef XWFEATURE_RELAY
static void relay_connd_curses( XWEnv xwe, void* closure, XP_UCHAR* const room,
XP_Bool reconnect, XP_U16 devOrder,
XP_Bool allHere, XP_U16 nMissing );
static void relay_status_curses( XWEnv xwe, void* closure, CommsRelayState state );
static void relay_error_curses( XWEnv xwe, void* closure, XWREASON relayErr );
static XP_Bool relay_sendNoConn_curses( XWEnv xwe, const XP_U8* msg, XP_U16 len,
const XP_UCHAR* msgNo,
const XP_UCHAR* relayID, void* closure );
#endif
static void curses_countChanged( XWEnv xwe, void* closure, XP_U16 newCount,
XP_Bool quashed );
static XP_U32 curses_getFlags( XWEnv xwe, void* closure );
#ifdef RELAY_VIA_HTTP
static void relay_requestJoin_curses( void* closure, const XP_UCHAR* devID,
const XP_UCHAR* room, XP_U16 nPlayersHere,
XP_U16 nPlayersTotal, XP_U16 seed, XP_U16 lang );
#endif
static XP_Bool rematch_and_save( CursesBoardGlobals* bGlobals, RematchOrder ro,
XP_U32* newGameIDP );
static void disposeBoard( CursesBoardGlobals* bGlobals, XP_Bool rmFromList );
static void initCP( CommonGlobals* cGlobals );
static CursesBoardGlobals* commonInit( CursesBoardState* cbState,
sqlite3_int64 rowid,
const CurGameInfo* gip );
CursesBoardState*
cb_init( CursesAppGlobals* aGlobals, LaunchParams* params,
CursesMenuState* menuState, OnGameSaved onGameSaved )
{
CursesBoardState* result = g_malloc0( sizeof(*result) );
result->aGlobals = aGlobals;
result->params = params;
result->menuState = menuState;
result->onGameSaved = onGameSaved;
return result;
}
void
cb_resized( CursesBoardState* cbState, const cb_dims* dims )
{
for ( GSList* iter = cbState->games; !!iter; iter = iter->next ) {
CursesBoardGlobals* one = (CursesBoardGlobals*)iter->data;
disposeDraw( one );
enableDraw( one, dims );
}
}
static gint
inviteIdle( gpointer data )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
LaunchParams* params = bGlobals->cGlobals.params;
if ( !!params->connInfo.sms.inviteePhones
#ifdef XWFEATURE_RELAY
|| !!params->connInfo.relay.inviteeRelayIDs
#endif
|| !!params->connInfo.mqtt.inviteeDevIDs ) {
sendInvite( bGlobals, 0 );
}
return FALSE;
}
void
cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, const cb_dims* dims )
{
LOG_FUNC();
CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid, NULL, NULL );
initMenus( bGlobals );
enableDraw( bGlobals, dims );
setupBoard( bGlobals );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
if ( !!cGlobals->game.comms ) {
comms_resendAll( cGlobals->game.comms, NULL_XWE, COMMS_CONN_NONE, XP_FALSE );
}
if ( bGlobals->cGlobals.params->forceInvite ) {
(void)ADD_ONETIME_IDLE( inviteIdle, bGlobals );
}
}
bool
cb_new( CursesBoardState* cbState, const cb_dims* dims, const CurGameInfo* gi,
XP_U32* newGameIDP )
{
CursesBoardGlobals* bGlobals = findOrOpen( cbState, -1, gi, NULL );
if ( !!bGlobals ) {
initMenus( bGlobals );
enableDraw( bGlobals, dims );
setupBoard( bGlobals );
}
XP_Bool success = NULL != bGlobals;
if ( success && !!newGameIDP ) {
*newGameIDP = bGlobals->cGlobals.gi->gameID;
}
return success;
}
void
cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
const cb_dims* XP_UNUSED(dims) )
{
LaunchParams* params = cbState->params;
CommsAddrRec selfAddr;
makeSelfAddress( &selfAddr, params );
CursesBoardGlobals* bGlobals = commonInit( cbState, -1, NULL );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
initCP( cGlobals );
if ( game_makeFromInvite( &cGlobals->game, NULL_XWE, nli, &selfAddr,
cGlobals->util, (DrawCtx*)NULL,
&cGlobals->cp, &cGlobals->procs ) ) {
linuxSaveGame( cGlobals );
} else {
XP_ASSERT( 0 );
}
disposeBoard( bGlobals, XP_TRUE );
}
const MenuList g_allMenuList[] = {
{ handleLeft, "Left", "H", 'H' },
{ handleRight, "Right", "L", 'L' },
{ handleUp, "Up", "J", 'J' },
{ handleDown, "Down", "K", 'K' },
{ handleClose, "Close", "W", 'W' },
{ handleSpace, "Raise focus", "<spc>", ' ' },
{ handleRet, "Tap", "<[alt-]ret>", '\r' },
{ handleShowVals, "Tile values", "T", 'T' },
};
const MenuList g_boardMenuList[] = {
#ifdef ALT_KEYS_WORKING
{ handleAltLeft, "Force left", "{", '{' },
{ handleAltRight, "Force right", "}", '}' },
{ handleAltUp, "Force up", "_", '_' },
{ handleAltDown, "Force down", "+", '+' },
#endif
{ handleHint, "Hint", "?", '?' },
{ handleCommit, "Commit move", "C", 'C' },
{ handleFlip, "Flip", "F", 'F' },
{ handleToggleValues, "Show values", "V", 'V' },
{ handleBackspace, "Remove from board", "<del>", 8 },
{ handleUndo, "Undo prev", "U", 'U' },
{ handleReplace, "uNdo cur", "N", 'N' },
{ sendInvite, "invitE", "E", 'E' },
{ NULL, NULL, NULL, '\0'}
};
const MenuList g_trayMenuList[] = {
{ handleJuggle, "Juggle", "G", 'G' },
{ handleHide, "[un]hIde", "I", 'I' },
#ifdef ALT_KEYS_WORKING
{ handleAltLeft, "Divider left", "{", '{' },
{ handleAltRight, "Divider right", "}", '}' },
#endif
{ NULL, NULL, NULL, '\0'}
};
const MenuList g_scoreMenuList[] = {
#ifdef KEYBOARD_NAV
#endif
{ NULL, NULL, NULL, '\0'}
};
#ifdef KEYBOARD_NAV
static void
changeMenuForFocus( CursesBoardGlobals* bGlobals, BoardObjectType focussed )
{
const MenuList* subMenu = NULL;
if ( focussed == OBJ_TRAY ) {
subMenu = g_trayMenuList;
} else if ( focussed == OBJ_BOARD ) {
subMenu = g_boardMenuList;
} else if ( focussed == OBJ_SCORE ) {
subMenu = g_scoreMenuList;
}
CursesMenuState* menuState = bGlobals->menuState;
if ( !!menuState ) {
cmenu_removeMenus( menuState, bGlobals->lastSubMenu, NULL );
bGlobals->lastSubMenu = subMenu;
cmenu_addMenus( menuState, bGlobals, subMenu, NULL );
}
// drawMenuLargeOrSmall( globals->apg, menuList, globals );
} /* changeMenuForFocus */
#endif
static void setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util );
static void
initMenus( CursesBoardGlobals* bGlobals )
{
if ( !bGlobals->menuState ) {
bGlobals->menuState = bGlobals->cbState->menuState;
cmenu_push( bGlobals->menuState, bGlobals, g_allMenuList, NULL );
}
}
static void
onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
/* onCursesGameSaved( bGlobals->aGlobals, rowid ); */
BoardCtxt* board = cGlobals->game.board;
board_invalAll( board );
board_draw( board, NULL_XWE );
/* May not be recorded */
XP_ASSERT( cGlobals->rowid == rowid );
// cGlobals->rowid = rowid;
CursesBoardState* cbState = bGlobals->cbState;
(*cbState->onGameSaved)( cbState->aGlobals, rowid, firstTime );
}
static gboolean
fire_acceptor( GIOChannel* source, GIOCondition condition, gpointer data )
{
if ( 0 != (G_IO_IN & condition) ) {
CursesBoardGlobals* globals = (CursesBoardGlobals*)data;
int fd = g_io_channel_unix_get_fd( source );
XP_ASSERT( fd == globals->csInfo.server.serverSocket );
(*globals->cGlobals.acceptor)( fd, globals );
}
return TRUE;
}
static void
curses_socket_acceptor( int listener, Acceptor func, CommonGlobals* cGlobals,
void** XP_UNUSED(storage) )
{
if ( -1 == listener ) {
XP_LOGFF( "removal of listener not implemented!!!!!" );
} else {
CursesBoardGlobals* globals = (CursesBoardGlobals*)cGlobals;
XP_ASSERT( !cGlobals->acceptor || (func == cGlobals->acceptor) );
cGlobals->acceptor = func;
globals->csInfo.server.serverSocket = listener;
ADD_SOCKET( globals, listener, fire_acceptor );
}
}
static void
initTProcsCurses( CommonGlobals* cGlobals )
{
cGlobals->procs.closure = cGlobals;
cGlobals->procs.sendMsgs = linux_send;
#ifdef XWFEATURE_COMMS_INVITE
cGlobals->procs.sendInvt = linux_send_invt;
#endif
#ifdef COMMS_HEARTBEAT
cGlobals->procs.reset = linux_reset;
#endif
#ifdef XWFEATURE_RELAY
cGlobals->procs.rstatus = relay_status_curses;
cGlobals->procs.rconnd = relay_connd_curses;
cGlobals->procs.rerror = relay_error_curses;
cGlobals->procs.sendNoConn = relay_sendNoConn_curses;
#endif
cGlobals->procs.countChanged = curses_countChanged;
cGlobals->procs.getFlags = curses_getFlags;
# ifdef RELAY_VIA_HTTP
cGlobals->procs.requestJoin = relay_requestJoin_curses;
#endif
}
static CursesBoardGlobals*
commonInit( CursesBoardState* cbState, sqlite3_int64 rowid,
const CurGameInfo* gip )
{
CursesBoardGlobals* bGlobals = g_malloc0( sizeof(*bGlobals) );
XP_LOGFF( "alloc'd bGlobals %p", bGlobals );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
LaunchParams* params = cbState->params;
cGlobals->gi = &cGlobals->_gi;
gi_copy( MPPARM(params->mpool) cGlobals->gi, !!gip? gip : &params->pgi );
cGlobals->rowid = rowid;
bGlobals->cbState = cbState;
cGlobals->params = params;
setupUtil( cGlobals );
setupCursesUtilCallbacks( bGlobals, cGlobals->util );
cGlobals->socketAddedClosure = bGlobals;
cGlobals->onSave = onGameSaved;
cGlobals->onSaveClosure = bGlobals;
cGlobals->addAcceptor = curses_socket_acceptor;
initTProcsCurses( cGlobals );
makeSelfAddress( &cGlobals->selfAddr, params );
setOneSecondTimer( cGlobals );
return bGlobals;
} /* commonInit */
static void
disposeDraw( CursesBoardGlobals* bGlobals )
{
if ( !!bGlobals->boardWin ) {
cursesDrawCtxtFree( bGlobals->cGlobals.draw );
wclear( bGlobals->boardWin );
wrefresh( bGlobals->boardWin );
delwin( bGlobals->boardWin );
}
}
static void
disposeBoard( CursesBoardGlobals* bGlobals, XP_Bool rmFromList )
{
XP_LOGFF( "passed bGlobals %p", bGlobals );
/* XP_ASSERT( 0 == bGlobals->refCount ); */
CommonGlobals* cGlobals = &bGlobals->cGlobals;
disposeDraw( bGlobals );
if ( !!bGlobals->cbState->menuState ) {
cmenu_pop( bGlobals->cbState->menuState );
}
clearOneSecondTimer( cGlobals );
gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) cGlobals->gi );
game_dispose( &cGlobals->game, NULL_XWE );
disposeUtil( cGlobals );
if ( rmFromList ) {
CursesBoardState* cbState = bGlobals->cbState;
cbState->games = g_slist_remove( cbState->games, bGlobals ); /* no!!! */
}
/* onCursesBoardClosing( bGlobals->aGlobals, bGlobals ); */
XP_LOGFF( "freeing globals: %p", bGlobals );
g_free( bGlobals );
}
static CursesBoardGlobals*
ref( CursesBoardGlobals* bGlobals )
{
++bGlobals->refCount;
XP_LOGFF( "refCount now %d", bGlobals->refCount );
return bGlobals;
}
static void
unref( CursesBoardGlobals* bGlobals )
{
--bGlobals->refCount;
XP_LOGFF( "refCount now %d", bGlobals->refCount );
if ( 0 == bGlobals->refCount ) {
disposeBoard( bGlobals, XP_TRUE );
}
}
static int
utf8_len( const char* face )
{
const int max = strlen(face);
int count = 0;
mbstate_t ps = {0};
for ( int offset = 0; offset < max; ) {
size_t nBytes = mbrlen( &face[offset], max - offset, &ps );
if ( 0 < nBytes ) {
++count;
offset += nBytes;
} else {
break;
}
}
return count;
}
static void
getFromDict( const DictionaryCtxt* dict, XP_U16* fontWidthP,
XP_U16* fontHtP )
{
int maxSide = 1;
for ( Tile tile = 0; tile < dict->nFaces; ++tile ) {
const XP_UCHAR* face = dict_getTileString( dict, tile );
int thisLen = utf8_len( face );
while ( thisLen > maxSide * maxSide ) {
++maxSide;
}
}
/* XP_LOGF( "%s(): width = %d", __func__, maxLen ); */
*fontWidthP = *fontHtP = maxSide;
}
static void
setupBoard( CursesBoardGlobals* bGlobals )
{
LOG_FUNC();
/* positionSizeStuff( bGlobals ); */
CommonGlobals* cGlobals = &bGlobals->cGlobals;
BoardCtxt* board = cGlobals->game.board;
const int width = bGlobals->winWidth;
const int height = bGlobals->winHeight;
XP_U16 fontWidth, fontHt;
const DictionaryCtxt* dict = model_getDictionary( cGlobals->game.model );
getFromDict( dict, &fontWidth, &fontHt );
BoardDims dims;
board_figureLayout( board, NULL_XWE, cGlobals->gi,
0, 0, width, height, 100,
150, 200, /* percents */
width*75/100,
fontWidth, fontHt,
XP_FALSE, &dims );
board_applyLayout( board, NULL_XWE, &dims );
XP_LOGFF( "calling board_draw()" );
board_invalAll( board );
board_draw( board, NULL_XWE );
}
static void
initCP( CommonGlobals* cGlobals )
{
const LaunchParams* params = cGlobals->params;
cGlobals->cp.showBoardArrow = XP_TRUE;
cGlobals->cp.showRobotScores = params->showRobotScores;
cGlobals->cp.hideTileValues = params->hideValues;
cGlobals->cp.skipMQTTAdd = params->skipMQTTAdd;
cGlobals->cp.skipCommitConfirm = params->skipCommitConfirm;
cGlobals->cp.sortNewTiles = params->sortNewTiles;
cGlobals->cp.showColors = params->showColors;
cGlobals->cp.allowPeek = params->allowPeek;
#ifdef XWFEATURE_SLOW_ROBOT
cGlobals->cp.robotThinkMin = params->robotThinkMin;
cGlobals->cp.robotThinkMax = params->robotThinkMax;
cGlobals->cp.robotTradePct = params->robotTradePct;
#endif
#ifdef XWFEATURE_ROBOTPHONIES
cGlobals->cp.makePhonyPct = params->makePhonyPct;
#endif
}
static CursesBoardGlobals*
initNoDraw( CursesBoardState* cbState, sqlite3_int64 rowid,
const CurGameInfo* gi, const CommsAddrRec* returnAddr )
{
LOG_FUNC();
CursesBoardGlobals* result = commonInit( cbState, rowid, gi );
CommonGlobals* cGlobals = &result->cGlobals;
if ( !!returnAddr ) {
cGlobals->hostAddr = *returnAddr;
}
initCP( cGlobals );
if ( linuxOpenGame( cGlobals ) ) {
result = ref( result );
} else {
disposeBoard( result, XP_TRUE );
result = NULL;
}
return result;
}
static void
enableDraw( CursesBoardGlobals* bGlobals, const cb_dims* dims )
{
LOG_FUNC();
XP_ASSERT( !!dims );
bGlobals->boardWin = newwin( dims->height, dims->width, dims->top, 0 );
getmaxyx( bGlobals->boardWin, bGlobals->winHeight, bGlobals->winWidth );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
if( !!bGlobals->boardWin ) {
cGlobals->draw = cursesDrawCtxtMake( bGlobals->boardWin );
board_setDraw( cGlobals->game.board, NULL_XWE, cGlobals->draw );
}
setupBoard( bGlobals );
}
CursesBoardGlobals*
findOrOpenForGameID( CursesBoardState* cbState, XP_U32 gameID,
const CurGameInfo* gi, const CommsAddrRec* returnAddr )
{
CursesBoardGlobals* result = NULL;
sqlite3_int64 rowids[1];
int nRowIDs = VSIZE(rowids);
gdb_getRowsForGameID( cbState->params->pDb, gameID, rowids, &nRowIDs );
if ( 1 == nRowIDs ) {
result = findOrOpen( cbState, rowids[0], gi, returnAddr );
}
return result;
}
const CommonGlobals*
cb_getForGameID( CursesBoardState* cbState, XP_U32 gameID )
{
CursesBoardGlobals* cbg = findOrOpenForGameID( cbState, gameID, NULL, NULL );
CommonGlobals* result = &cbg->cGlobals;
// XP_LOGFF( "(%X) => %p", gameID, result );
return result;
}
static CursesBoardGlobals*
findOrOpen( CursesBoardState* cbState, sqlite3_int64 rowid,
const CurGameInfo* gi, const CommsAddrRec* returnAddr )
{
CursesBoardGlobals* result = NULL;
for ( GSList* iter = cbState->games;
rowid >= 0 && !!iter && !result; iter = iter->next ) {
CursesBoardGlobals* one = (CursesBoardGlobals*)iter->data;
if ( one->cGlobals.rowid == rowid ) {
result = one;
}
}
if ( !result ) {
result = initNoDraw( cbState, rowid, gi, returnAddr );
if ( !!result ) {
setupBoard( result );
cbState->games = g_slist_append( cbState->games, result );
}
}
return result;
}
bool
cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, XP_U16 expectSeed,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from )
{
LOG_FUNC();
CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid, NULL, NULL );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms );
bool success = 0 == expectSeed || seed == expectSeed;
if ( success ) {
gameGotBuf( cGlobals, XP_TRUE, buf, len, from );
} else {
XP_LOGFF( "msg for seed %d but I opened %d", expectSeed, seed );
}
return success;
}
void
cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from )
{
sqlite3_int64 rowids[4];
int nRows = VSIZE( rowids );
LaunchParams* params = cbState->params;
gdb_getRowsForGameID( params->pDb, gameID, rowids, &nRows );
XP_LOGFF( "found %d rows for gameID %X", nRows, gameID );
for ( int ii = 0; ii < nRows; ++ii ) {
#ifdef DEBUG
bool success =
#endif
cb_feedRow( cbState, rowids[ii], 0, buf, len, from );
XP_ASSERT( success );
}
}
void
cb_addInvites( CursesBoardState* cbState, XP_U32 gameID, XP_U16 nRemotes,
XP_U16 forceChannels[], const CommsAddrRec destAddrs[] )
{
CursesBoardGlobals* bGlobals = findOrOpenForGameID( cbState, gameID, NULL, NULL );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
CommsCtxt* comms = cGlobals->game.comms;
CommsAddrRec selfAddr;
comms_getSelfAddr( comms, &selfAddr );
for ( int ii = 0; ii < nRemotes; ++ii ) {
NetLaunchInfo nli;
nli_init( &nli, cGlobals->gi, &selfAddr, 1, forceChannels[ii] );
comms_invite( comms, NULL_XWE, &nli, &destAddrs[ii], XP_TRUE );
}
}
XP_Bool
cb_makeRematch( CursesBoardState* cbState, XP_U32 gameID, RematchOrder ro,
XP_U32* newGameIDP )
{
CursesBoardGlobals* bGlobals = findOrOpenForGameID( cbState, gameID,
NULL, NULL );
XP_Bool success = rematch_and_save( bGlobals, ro, newGameIDP );
return success;
}
XP_Bool
cb_makeMoveIf( CursesBoardState* cbState, XP_U32 gameID )
{
CursesBoardGlobals* bGlobals =
findOrOpenForGameID( cbState, gameID, NULL, NULL );
XP_Bool success = !!bGlobals;
if ( success ) {
CommonGlobals* cGlobals = &bGlobals->cGlobals;
BoardCtxt* board = cGlobals->game.board;
success = board_canHint( board );
if ( success ) {
XP_Bool ignored;
success = board_requestHint( cGlobals->game.board, NULL_XWE,
#ifdef XWFEATURE_SEARCHLIMIT
XP_FALSE,
#endif
XP_FALSE, &ignored );
if ( !success ) {
XP_LOGFF( "unable to find hint; so PASSing" );
}
success = board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE,
NULL );
}
}
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}
static void
kill_board( gpointer data )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
linuxSaveGame( &bGlobals->cGlobals );
disposeBoard( bGlobals, XP_FALSE );
}
void
cb_closeAll( CursesBoardState* cbState )
{
g_slist_free_full( cbState->games, kill_board );
}
static void
cursesUserError( CursesBoardGlobals* bGlobals, const char* format, ... )
{
char buf[512];
va_list ap;
va_start( ap, format );
vsprintf( buf, format, ap );
va_end(ap);
if ( !!bGlobals->boardWin ) {
(void)ca_inform( bGlobals->boardWin, buf );
} else {
XP_LOGFF( "(msg=%s)", buf );
}
} /* cursesUserError */
static void
curses_util_notifyPickTileBlank( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 playerNum,
XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row),
const XP_UCHAR** texts, XP_U16 nTiles )
{
CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure;
char query[128];
char* playerName = globals->cGlobals.gi->players[playerNum].name;
snprintf( query, sizeof(query),
"Pick tile for %s! (Tab or type letter to select "
"then hit <cr>.)", playerName );
/*index = */curses_askLetter( globals->boardWin, query, texts, nTiles );
// return index;
} /* util_userPickTile */
static void
curses_util_informNeedPickTiles( XW_UtilCtxt* XP_UNUSED(uc),
XWEnv XP_UNUSED(xwe),
XP_Bool XP_UNUSED(isInitial),
XP_U16 XP_UNUSED(player),
XP_U16 XP_UNUSED(nToPick),
XP_U16 XP_UNUSED(nFaces),
const XP_UCHAR** XP_UNUSED(faces),
const XP_U16* XP_UNUSED(counts) )
{
XP_ASSERT(0);
/* CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; */
/* char query[128]; */
/* XP_S16 index; */
/* char* playerName = globals->cGlobals.gi->players[playerNum].name; */
/* snprintf( query, sizeof(query), */
/* "Pick tile for %s! (Tab or type letter to select " */
/* "then hit <cr>.)", playerName ); */
/* index = curses_askLetter( globals, query, texts, nTiles ); */
/* return index; */
}
static void
curses_util_userError( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), UtilErrID id )
{
CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure;
XP_Bool silent;
const XP_UCHAR* message = linux_getErrString( id, &silent );
if ( silent ) {
XP_LOGFF( "silent userError: %s", message );
} else {
cursesUserError( globals, message );
}
} /* curses_util_userError */
static gint
ask_move( gpointer data )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
const char* answers[] = {"Ok", "Cancel", NULL};
if (0 == cursesask(bGlobals->boardWin, cGlobals->question,
VSIZE(answers)-1, answers) ) {
BoardCtxt* board = cGlobals->game.board;
if ( board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE, NULL ) ) {
board_draw( board, NULL_XWE );
linuxSaveGame( &bGlobals->cGlobals );
}
}
return FALSE;
}
static void
curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc),
XWEnv XP_UNUSED(xwe),
XP_U16 XP_UNUSED_DBG(playerNum),
const XP_UCHAR* XP_UNUSED_DBG(name) )
{
XP_WARNF( "curses_util_informNeedPassword(num=%d, name=%s", playerNum, name );
XP_ASSERT(0);
} /* curses_util_askPassword */
static void
curses_util_yOffsetChange( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XP_U16 XP_UNUSED(maxOffset),
XP_U16 oldOffset, XP_U16 newOffset )
{
if ( 0 != newOffset ) {
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
if ( !!bGlobals->boardWin ) {
ca_informf( bGlobals->boardWin, "%s(oldOffset=%d, newOffset=%d)", __func__,
oldOffset, newOffset );
}
}
} /* curses_util_yOffsetChange */
#ifdef XWFEATURE_TURNCHANGENOTIFY
static void
curses_util_turnChanged( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XP_S16 XP_UNUSED_DBG(newTurn) )
{
XP_LOGFF( "(newTurn=%d)", newTurn );
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
linuxSaveGame( &bGlobals->cGlobals );
}
#endif
static void
curses_util_notifyIllegalWords( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), BadWordInfo* bwi,
XP_U16 player, XP_Bool turnLost )
{
gchar* strs = g_strjoinv( "\", \"", (gchar**)bwi->words );
gchar* msg = g_strdup_printf( "Player %d played bad word[s]: \"%s\". "
"Turn lost: %s", player, strs, boolToStr(turnLost) );
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
if ( !!bGlobals->boardWin ) {
ca_inform( bGlobals->boardWin, msg );
} else {
XP_LOGFF( "msg: %s", msg );
}
g_free( strs );
g_free( msg );
} /* curses_util_notifyIllegalWord */
/* this needs to change!!! */
static void
curses_util_notifyMove( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XWStreamCtxt* stream )
{
CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure;
CommonGlobals* cGlobals = &globals->cGlobals;
XP_U16 len = stream_getSize( stream );
XP_ASSERT( len <= VSIZE(cGlobals->question) );
stream_getBytes( stream, cGlobals->question, len );
cGlobals->question[len] = '\0';
(void)ADD_ONETIME_IDLE( ask_move, globals );
} /* curses_util_not */
static gint
ask_trade( gpointer data )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
const char* buttons[] = { "Ok", "Cancel" };
if (0 == cursesask( bGlobals->boardWin, cGlobals->question,
VSIZE(buttons), buttons ) ) {
BoardCtxt* board = cGlobals->game.board;
if ( board_commitTurn( board, NULL_XWE, XP_TRUE, XP_TRUE, NULL ) ) {
board_draw( board, NULL_XWE );
linuxSaveGame( cGlobals );
}
}
return FALSE;
}
static void
curses_util_notifyTrade( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
const XP_UCHAR** tiles, XP_U16 nTiles )
{
CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure;
formatConfirmTrade( &globals->cGlobals, tiles, nTiles );
(void)ADD_ONETIME_IDLE( ask_trade, globals );
}
static void
curses_util_trayHiddenChange( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe),
XW_TrayVisState XP_UNUSED(state),
XP_U16 XP_UNUSED(nVisibleRows) )
{
/* nothing to do if we don't have a scrollbar */
} /* curses_util_trayHiddenChange */
static void
cursesShowFinalScores( CursesBoardGlobals* bGlobals )
{
XWStreamCtxt* stream;
XP_UCHAR* text;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr );
server_writeFinalScores( cGlobals->game.server, NULL_XWE, stream );
text = strFromStream( stream );
(void)ca_inform( bGlobals->boardWin, text );
free( text );
stream_destroy( stream );
} /* cursesShowFinalScores */
static void
curses_util_informMove( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XP_S16 XP_UNUSED(turn), XWStreamCtxt* expl,
XWStreamCtxt* XP_UNUSED(words))
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
if ( !!bGlobals->boardWin ) {
char* question = strFromStream( expl );
(void)ca_inform( bGlobals->boardWin, question );
free( question );
}
}
static void
curses_util_notifyDupStatus( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XP_Bool XP_UNUSED(amHost),
const XP_UCHAR* msg )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
if ( !!bGlobals->boardWin ) {
ca_inform( bGlobals->boardWin, msg );
}
}
static void
curses_util_informUndo( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
if ( !!bGlobals->boardWin ) {
ca_inform( bGlobals->boardWin, "informUndo(): undo was done" );
}
}
static void
rematch_and_save_once( CursesBoardGlobals* bGlobals, RematchOrder ro )
{
LOG_FUNC();
CommonGlobals* cGlobals = &bGlobals->cGlobals;
int32_t alreadyDone;
gchar key[128];
snprintf( key, sizeof(key), "%X/rematch_done", cGlobals->gi->gameID );
if ( gdb_fetchInt( cGlobals->params->pDb, key, &alreadyDone )
&& 0 != alreadyDone ) {
XP_LOGFF( "already rematched game %X", cGlobals->gi->gameID );
} else {
if ( rematch_and_save( bGlobals, ro, NULL ) ) {
gdb_storeInt( cGlobals->params->pDb, key, 1 );
}
}
LOG_RETURN_VOID();
}
static XP_Bool
rematch_and_save( CursesBoardGlobals* bGlobals, RematchOrder ro,
XP_U32* newGameIDP )
{
LOG_FUNC();
CommonGlobals* cGlobals = &bGlobals->cGlobals;
CursesBoardState* cbState = bGlobals->cbState;
CursesBoardGlobals* bGlobalsNew = commonInit( cbState, -1, NULL );
XP_Bool success = game_makeRematch( &bGlobals->cGlobals.game, NULL_XWE,
bGlobalsNew->cGlobals.util,
&cGlobals->cp, &bGlobalsNew->cGlobals.procs,
&bGlobalsNew->cGlobals.game, "newName", ro );
if ( success ) {
if ( !!newGameIDP ) {
*newGameIDP = bGlobalsNew->cGlobals.gi->gameID;
}
linuxSaveGame( &bGlobalsNew->cGlobals );
}
disposeBoard( bGlobalsNew, XP_TRUE );
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}
static void
curses_util_notifyGameOver( XW_UtilCtxt* uc, XWEnv xwe, XP_S16 quitter )
{
LOG_FUNC();
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
LaunchParams* params = cGlobals->params;
board_draw( cGlobals->game.board, xwe );
/* game belongs in cGlobals... */
if ( params->printHistory ) {
catGameHistory( cGlobals );
}
catFinalScores( cGlobals, quitter );
if ( params->quitAfter >= 0 ) {
sleep( params->quitAfter );
handleQuit( bGlobals->cbState->aGlobals, 0 );
} else if ( params->undoWhenDone ) {
server_handleUndo( cGlobals->game.server, xwe, 0 );
} else if ( !params->skipGameOver && !!bGlobals->boardWin ) {
/* This is modal. Don't show if quitting */
cursesShowFinalScores( bGlobals );
}
if ( params->rematchOnDone ) {
XP_ASSERT( !!params->rematchOrder );
rematch_and_save_once( bGlobals, roFromStr(params->rematchOrder) );
}
} /* curses_util_notifyGameOver */
static void
curses_util_informNetDict( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
const XP_UCHAR* XP_UNUSED(isoCode),
const XP_UCHAR* XP_UNUSED_DBG(oldName),
const XP_UCHAR* XP_UNUSED_DBG(newName),
const XP_UCHAR* XP_UNUSED_DBG(newSum),
XWPhoniesChoice phoniesAction )
{
XP_USE(uc);
XP_USE(phoniesAction);
XP_LOGFF( "%s => %s (cksum: %s)", oldName, newName, newSum );
}
#ifdef XWFEATURE_SEARCHLIMIT
static XP_Bool
curses_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc),XWEnv XP_UNUSED(xwe),
XP_U16* XP_UNUSED(min), XP_U16* XP_UNUSED(max) )
{
XP_ASSERT(0);
return XP_TRUE;
}
#endif
#ifdef XWFEATURE_HILITECELL
static XP_Bool
curses_util_hiliteCell( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row) )
{
CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure;
if ( globals->cGlobals.params->sleepOnAnchor ) {
usleep( 10000 );
}
return XP_TRUE;
} /* curses_util_hiliteCell */
#endif
static XP_Bool
curses_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe) )
{
return XP_TRUE;
} /* curses_util_engineProgressCallback */
static XP_Bool
curses_util_altKeyDown( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe) )
{
return XP_FALSE;
}
static void
curses_util_remSelected( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
XWStreamCtxt* stream;
XP_UCHAR* text;
stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr );
board_formatRemainingTiles( cGlobals->game.board, NULL_XWE, stream );
text = strFromStream( stream );
(void)ca_inform( bGlobals->boardWin, text );
free( text );
}
static void
curses_util_getMQTTIDsFor( XW_UtilCtxt* uc, XWEnv xwe, XP_U16 nRelayIDs,
const XP_UCHAR* relayIDs[] )
{
XP_ASSERT(0); /* implement me */
XP_USE( uc );
XP_USE( xwe );
XP_USE( nRelayIDs );
XP_USE( relayIDs );
}
static void
curses_util_timerSelected( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe),
XP_Bool XP_UNUSED_DBG(inDuplicateMode),
XP_Bool XP_UNUSED_DBG(canPause) )
{
XP_LOGFF( "(inDuplicateMode=%d, canPause=%d)", inDuplicateMode, canPause );
}
static void
curses_util_bonusSquareHeld( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XWBonusType bonus )
{
LOG_FUNC();
XP_USE( uc );
XP_USE( bonus );
}
static void
curses_util_playerScoreHeld( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe), XP_U16 player )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
LastMoveInfo lmi;
if ( model_getPlayersLastScore( bGlobals->cGlobals.game.model, NULL_XWE,
player, &lmi ) ) {
XP_UCHAR buf[128];
formatLMI( &lmi, buf, VSIZE(buf) );
(void)ca_inform( bGlobals->boardWin, buf );
}
}
#ifdef XWFEATURE_BOARDWORDS
static void
curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XWStreamCtxt* words )
{
XP_USE( uc );
catOnClose( words, NULL_XWE, NULL );
fprintf( stderr, "\n" );
}
#endif
static void
curses_util_informWordsBlocked( XW_UtilCtxt* XP_UNUSED(uc), XWEnv XP_UNUSED(xwe),
XP_U16 XP_UNUSED_DBG(nBadWords),
XWStreamCtxt* XP_UNUSED(words),
const XP_UCHAR* XP_UNUSED_DBG(dictName) )
{
XP_LOGFF( "(nBadWords=%d, dict=%s)", nBadWords, dictName );
}
#ifdef XWFEATURE_CHAT
static void
curses_util_showChat( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
const XP_UCHAR* const XP_UNUSED_DBG(msg),
XP_S16 XP_UNUSED_DBG(from), XP_U32 XP_UNUSED(timestamp) )
{
CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure;
globals->nChatsSent = 0;
# ifdef DEBUG
const XP_UCHAR* name = "<unknown>";
if ( 0 <= from ) {
CommonGlobals* cGlobals = &globals->cGlobals;
name = cGlobals->gi->players[from].name;
}
XP_LOGFF( "got \"%s\" from %s", msg, name );
# endif
}
#endif
static void
setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util )
{
util->closure = bGlobals;
#define SET_PROC(NAM) util->vtable->m_util_##NAM = curses_util_##NAM
SET_PROC(userError);
SET_PROC(notifyMove);
SET_PROC(notifyTrade);
SET_PROC(notifyPickTileBlank);
SET_PROC(informNeedPickTiles);
SET_PROC(informNeedPassword);
SET_PROC(trayHiddenChange);
SET_PROC(yOffsetChange);
#ifdef XWFEATURE_TURNCHANGENOTIFY
SET_PROC(turnChanged);
#endif
SET_PROC(notifyDupStatus);
SET_PROC(informMove);
SET_PROC(informUndo);
SET_PROC(informNetDict);
SET_PROC(notifyGameOver);
#ifdef XWFEATURE_HILITECELL
SET_PROC(hiliteCell);
#endif
SET_PROC(engineProgressCallback);
SET_PROC(altKeyDown); /* ?? */
SET_PROC(notifyIllegalWords);
SET_PROC(remSelected);
SET_PROC(getMQTTIDsFor);
SET_PROC(timerSelected);
#ifndef XWFEATURE_MINIWIN
SET_PROC(bonusSquareHeld);
SET_PROC(playerScoreHeld);
#endif
#ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld);
#endif
SET_PROC(informWordsBlocked);
#ifdef XWFEATURE_SEARCHLIMIT
SET_PROC(getTraySearchLimits);
#endif
#ifdef XWFEATURE_CHAT
SET_PROC(showChat);
#endif
#undef SET_PROC
assertTableFull( util->vtable, sizeof(*util->vtable), "curses util" );
} /* setupCursesUtilCallbacks */
static bool
handleFlip( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
if ( board_flip( cGlobals->game.board ) ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleFlip */
static bool
handleToggleValues( void* XP_UNUSED(closure), int XP_UNUSED(key) )
{
XP_ASSERT( 0 );
return XP_TRUE;
} /* handleToggleValues */
static bool
handleBackspace( void* closure, int XP_UNUSED(key) )
{
CommonGlobals* cGlobals = &((CursesBoardGlobals*)closure)->cGlobals;
XP_Bool handled;
if ( board_handleKey( cGlobals->game.board, NULL_XWE,
XP_CURSOR_KEY_DEL, &handled ) ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleBackspace */
static bool
handleUndo( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
if ( server_handleUndo( cGlobals->game.server, NULL_XWE, 0 ) ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleUndo */
static bool
handleReplace( void* closure, int XP_UNUSED(key) )
{
CommonGlobals* cGlobals = &((CursesBoardGlobals*)closure)->cGlobals;
if ( board_replaceTiles( cGlobals->game.board, NULL_XWE ) ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleReplace */
static bool
inviteList( CommonGlobals* cGlobals, CommsAddrRec* myAddr, GSList* invitees,
CommsConnType typ )
{
bool haveAddressees = !!invitees;
if ( haveAddressees ) {
LaunchParams* params = cGlobals->params;
#ifdef XWFEATURE_COMMS_INVITE
CommsAddrRec destAddr = {0};
#endif
for ( int ii = 0; ii < g_slist_length(invitees); ++ii ) {
const XP_U16 nPlayersH = params->connInfo.inviteeCounts[ii];
const gint forceChannel = ii + 1;
XP_LOGFF( "using nPlayersH of %d, forceChannel of %d for guest device %d",
nPlayersH, forceChannel, ii );
NetLaunchInfo nli;
nli_init( &nli, cGlobals->gi, myAddr, nPlayersH, forceChannel );
switch ( typ ) {
#ifdef XWFEATURE_RELAY
case COMMS_CONN_RELAY: {
uint64_t inviteeRelayID = *(uint64_t*)g_slist_nth_data( invitees, ii );
relaycon_invite( params, (XP_U32)inviteeRelayID, NULL, &nli );
}
break;
#endif
case COMMS_CONN_SMS: {
const gchar* inviteePhone = (const gchar*)g_slist_nth_data( invitees, ii );
#ifdef XWFEATURE_COMMS_INVITE
addr_addType( &destAddr, COMMS_CONN_SMS );
destAddr.u.sms.port = params->connInfo.sms.port;
XP_STRNCPY( destAddr.u.sms.phone, inviteePhone,
sizeof(destAddr.u.sms.phone) );
#else
linux_sms_invite( params, &nli, inviteePhone,
params->connInfo.sms.port );
#endif
}
break;
case COMMS_CONN_MQTT: {
MQTTDevID devID;
const gchar* str = g_slist_nth_data( invitees, ii );
if ( strToMQTTCDevID( str, &devID ) ) {
#ifdef XWFEATURE_COMMS_INVITE
addr_addType( &destAddr, COMMS_CONN_MQTT );
destAddr.u.mqtt.devID = devID;
#else
mqttc_invite( params, 0, &nli, &devID );
#endif
} else {
XP_LOGFF( "unable to convert devid %s", str );
}
}
break;
default:
XP_ASSERT(0);
}
#ifdef XWFEATURE_COMMS_INVITE
comms_invite( cGlobals->game.comms, NULL_XWE, &nli,
&destAddr, XP_TRUE );
#endif
}
}
if ( haveAddressees ) {
XP_LOGFF( "worked for %s", ConnType2Str( typ ) );
}
return haveAddressees;
}
static bool
sendInvite( void* closure, int XP_UNUSED(key) )
{
LOG_FUNC();
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
LaunchParams* params = cGlobals->params;
CommsAddrRec selfAddr;
CommsCtxt* comms = cGlobals->game.comms;
XP_ASSERT( comms );
comms_getSelfAddr( comms, &selfAddr );
gint forceChannel = 1;
const XP_U16 nPlayers = params->connInfo.inviteeCounts[forceChannel-1];
NetLaunchInfo nli;
nli_init( &nli, cGlobals->gi, &selfAddr, nPlayers, forceChannel );
if ( SERVER_ISHOST != cGlobals->gi->serverRole ) {
ca_inform( bGlobals->boardWin, "Only hosts can invite" );
/* Invite first based on an invitee provided. Otherwise, fall back to
doing a send-to-self. Let the recipient code reject a duplicate if
the user so desires. */
} else if ( inviteList( cGlobals, &selfAddr, params->connInfo.sms.inviteePhones,
COMMS_CONN_SMS ) ) {
/* do nothing */
#ifdef XWFEATURE_RELAY
} else if ( inviteList( cGlobals, &selfAddr, params->connInfo.relay.inviteeRelayIDs,
COMMS_CONN_RELAY ) ) {
/* do nothing */
#endif
} else if ( inviteList( cGlobals, &selfAddr, params->connInfo.mqtt.inviteeDevIDs,
COMMS_CONN_MQTT ) ) {
/* do nothing */
/* Try sending to self, using the phone number or relayID of this device */
/* <<<<<<< HEAD */
} else if ( addr_hasType( &selfAddr, COMMS_CONN_SMS ) ) {
linux_sms_invite( params, &nli, selfAddr.u.sms.phone, selfAddr.u.sms.port );
} else if ( addr_hasType( &selfAddr, COMMS_CONN_MQTT ) ) {
mqttc_invite( params, &nli, mqttc_getDevID( params ) );
#ifdef XWFEATURE_RELAY
} else if ( addr_hasType( &selfAddr, COMMS_CONN_RELAY ) ) {
/* ======= */
/* } else if ( addr_hasType( &addr, COMMS_CONN_SMS ) ) { */
/* linux_sms_invite( params, &nli, addr.u.sms.phone, addr.u.sms.port ); */
/* } else if ( addr_hasType( &addr, COMMS_CONN_MQTT ) ) { */
/* mqttc_invite( params, 0, &nli, mqttc_getDevID( params ) ); */
/* } else if ( addr_hasType( &addr, COMMS_CONN_RELAY ) ) { */
/* >>>>>>> d9781d21e (snapshot: mqtt invites for gtk work via comms) */
XP_U32 relayID = linux_getDevIDRelay( params );
if ( 0 != relayID ) {
relaycon_invite( params, relayID, NULL, &nli );
}
#endif
} else {
ca_inform( bGlobals->boardWin, "Cannot invite via relayID, MQTT or by \"sms phone\"." );
}
LOG_RETURNF( "%s", "TRUE" );
return XP_TRUE;
}
static bool
handleCommit( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
if ( board_commitTurn( cGlobals->game.board, NULL_XWE, XP_FALSE,
XP_FALSE, NULL ) ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleCommit */
static bool
handleJuggle( void* closure, int XP_UNUSED(key) )
{
CommonGlobals* cGlobals = &((CursesBoardGlobals*)closure)->cGlobals;
if ( board_juggleTray( cGlobals->game.board, NULL_XWE ) ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleJuggle */
static bool
handleHide( void* closure, int XP_UNUSED(key) )
{
CommonGlobals* cGlobals = &((CursesBoardGlobals*)closure)->cGlobals;
XW_TrayVisState curState =
board_getTrayVisState( cGlobals->game.board );
bool draw = curState == TRAY_REVEALED
? board_hideTray( cGlobals->game.board, NULL_XWE )
: board_showTray( cGlobals->game.board, NULL_XWE );
if ( draw ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleJuggle */
#ifdef KEYBOARD_NAV
static void
checkAssignFocus( BoardCtxt* board )
{
if ( OBJ_NONE == board_getFocusOwner(board) ) {
board_focusChanged( board, NULL_XWE, OBJ_BOARD, XP_TRUE );
}
}
static XP_Bool
handleFocusKey( CursesBoardGlobals* bGlobals, XP_Key key )
{
CommonGlobals* cGlobals = &bGlobals->cGlobals;
checkAssignFocus( cGlobals->game.board );
XP_Bool handled;
XP_Bool draw = board_handleKey( cGlobals->game.board, NULL_XWE,
key, &handled );
if ( !handled ) {
BoardObjectType nxt;
BoardObjectType order[] = { OBJ_BOARD, OBJ_SCORE, OBJ_TRAY };
draw = linShiftFocus( cGlobals, key, order, &nxt ) || draw;
if ( nxt != OBJ_NONE ) {
changeMenuForFocus( bGlobals, nxt );
}
}
if ( draw ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
} /* handleFocusKey */
#ifdef ALT_KEYS_WORKING
static bool
handleAltLeft( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTLEFT );
}
static bool
handleAltRight( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTRIGHT );
}
static bool
handleAltUp( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTUP );
}
static bool
handleAltDown( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTDOWN );
}
#endif
static bool
handleHint( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
XP_Bool redo;
XP_Bool draw = board_requestHint( cGlobals->game.board, NULL_XWE,
#ifdef XWFEATURE_SEARCHLIMIT
XP_FALSE,
#endif
XP_FALSE, &redo );
if ( draw ) {
board_draw( cGlobals->game.board, NULL_XWE );
}
return XP_TRUE;
}
static bool
handleLeft( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_LEFT );
} /* handleLeft */
static bool
handleRight( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_RIGHT );
} /* handleRight */
static bool
handleUp( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_UP );
} /* handleUp */
static bool
handleDown( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
return handleFocusKey( bGlobals, XP_CURSOR_KEY_DOWN );
} /* handleDown */
static gboolean
idle_close_game( gpointer data )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
linuxSaveGame( &bGlobals->cGlobals );
unref( bGlobals );
return FALSE;
}
static bool
handleClose( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
(void)ADD_ONETIME_IDLE( idle_close_game, bGlobals );
return XP_TRUE;
} /* handleDown */
static bool
handleSpace( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
BoardCtxt* board = bGlobals->cGlobals.game.board;
checkAssignFocus( board );
XP_Bool handled;
(void)board_handleKey( board, NULL_XWE, XP_RAISEFOCUS_KEY, &handled );
board_draw( board, NULL_XWE );
return XP_TRUE;
} /* handleSpace */
static bool
handleRet( void* closure, int key )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
BoardCtxt* board = bGlobals->cGlobals.game.board;
XP_Bool handled;
XP_Key xpKey = (key & ALT_BIT) == 0 ? XP_RETURN_KEY : XP_ALTRETURN_KEY;
if ( board_handleKey( board, NULL_XWE, xpKey, &handled ) ) {
board_draw( board, NULL_XWE );
}
return XP_TRUE;
} /* handleRet */
static bool
handleShowVals( void* closure, int XP_UNUSED(key) )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure;
CommonGlobals* cGlobals = &bGlobals->cGlobals;
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr );
server_formatDictCounts( bGlobals->cGlobals.game.server, NULL_XWE,
stream, 5, XP_FALSE );
const XP_U8* data = stream_getPtr( stream );
XP_U16 len = stream_getSize( stream );
XP_UCHAR buf[len + 1];
XP_MEMCPY( buf, data, len );
buf[len] = '\0';
(void)ca_inform( bGlobals->boardWin, buf );
stream_destroy( stream );
return XP_TRUE;
}
#endif
#ifdef XWFEATURE_RELAY
static void
relay_connd_curses( XWEnv XP_UNUSED(xwe), void* XP_UNUSED(closure), XP_UCHAR* const XP_UNUSED(room),
XP_Bool XP_UNUSED(reconnect), XP_U16 XP_UNUSED(devOrder),
XP_Bool XP_UNUSED_DBG(allHere),
XP_U16 XP_UNUSED_DBG(nMissing) )
{
XP_LOGFF( "got allHere: %s; nMissing: %d", allHere?"true":"false", nMissing );
}
static void
relay_error_curses( XWEnv XP_UNUSED(xwe), void* XP_UNUSED(closure),
XWREASON XP_UNUSED_DBG(relayErr) )
{
XP_LOGFF( "(%s)", XWREASON2Str( relayErr ) );
}
static XP_Bool
relay_sendNoConn_curses( XWEnv XP_UNUSED(xwe), const XP_U8* msg, XP_U16 len,
const XP_UCHAR* XP_UNUSED(msgNo),
const XP_UCHAR* relayID, void* closure )
{
XP_Bool success = XP_FALSE;
CommonGlobals* cGlobals = (CommonGlobals*)closure;
LaunchParams* params = cGlobals->params;
if ( params->useUdp /*&& !cGlobals->draw*/ ) {
XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms );
XP_U32 clientToken = makeClientToken( cGlobals->rowid, seed );
XP_S16 nSent = relaycon_sendnoconn( params, msg, len, relayID,
clientToken );
success = nSent == len;
}
return success;
} /* relay_sendNoConn_curses */
#endif
#ifdef RELAY_VIA_HTTP
static void
relay_requestJoin_curses( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room,
XP_U16 nPlayersHere, XP_U16 nPlayersTotal,
XP_U16 seed, XP_U16 lang )
{
CommonGlobals* cGlobals = (CommonGlobals*)closure;
relaycon_join( cGlobals->params, devID, room, nPlayersHere, nPlayersTotal,
seed, lang, onJoined, globals );
}
#endif
static void
curses_countChanged( XWEnv XP_UNUSED(xwe), void* XP_UNUSED(closure),
XP_U16 XP_UNUSED_DBG(newCount),
XP_Bool XP_UNUSED_DBG(quashed) )
{
/* discon_ok2.py depends on this log entry */
XP_LOGFF( "(newCount=%d, quashed=%s)", newCount, boolToStr(quashed) );
}
#ifdef COMMS_XPORT_FLAGSPROC
static XP_U32
curses_getFlags( XWEnv XP_UNUSED(xwe), void* XP_UNUSED(closure) )
{
return COMMS_XPORT_FLAGS_HASNOCONN;
}
#endif
#ifdef XWFEATURE_RELAY
static void
relay_status_curses( XWEnv XP_UNUSED(xwe), void* XP_UNUSED(closure),
CommsRelayState XP_UNUSED_DBG(state) )
{
/* CommonGlobals* cGlobals = (CommonGlobals*)closure; */
// bGlobals->commsRelayState = state;
XP_LOGFF( "got status: %s", CommsRelayState2Str(state) );
}
#endif