mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-02-05 20:45:49 +01:00
new script to run many games per device
I need it to be much closer to Android....
This commit is contained in:
parent
47004a0e08
commit
55e36e10a7
23 changed files with 985 additions and 303 deletions
|
@ -1378,13 +1378,32 @@ comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
|
|||
XP_ASSERT( found );
|
||||
}
|
||||
|
||||
typedef struct _NonAcks {
|
||||
int count;
|
||||
} NonAcks;
|
||||
|
||||
static ForEachAct
|
||||
countNonAcks( MsgQueueElem* elem, void* closure )
|
||||
{
|
||||
if ( IS_INVITE(elem) || 0 != elem->msgID ) {
|
||||
NonAcks* nap = (NonAcks*)closure;
|
||||
++nap->count;
|
||||
}
|
||||
return FEA_OK;
|
||||
}
|
||||
|
||||
XP_U16
|
||||
comms_countPendingPackets( const CommsCtxt* comms, XP_Bool* quashed )
|
||||
{
|
||||
if ( !!quashed ) {
|
||||
*quashed = QUASHED(comms);
|
||||
}
|
||||
return comms->queueLen;
|
||||
|
||||
NonAcks na = {0};
|
||||
forEachElem( (CommsCtxt*)comms, countNonAcks, &na );
|
||||
|
||||
// XP_LOGFF( "=> %d (queueLen = %d)", na.count, comms->queueLen );
|
||||
return na.count;
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
|
@ -2933,8 +2952,7 @@ validateChannelMessage( CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* addr,
|
|||
} else if ( msgID != rec->lastMsgRcd + 1 ) {
|
||||
XP_LOGFF( TAGFMT() "expected %d, got %d", TAGPRMS,
|
||||
rec->lastMsgRcd + 1, msgID );
|
||||
// Add this if adding ACK to queue isn't enough
|
||||
// ackAnyImpl( comms, xwe, XP_TRUE );
|
||||
ackAnyImpl( comms, xwe, XP_TRUE );
|
||||
rec = NULL;
|
||||
}
|
||||
} else {
|
||||
|
@ -3572,7 +3590,7 @@ rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
|
|||
// addr_setTypes( &recs->addr, addr_getTypes( &comms->selfAddr ) );
|
||||
}
|
||||
}
|
||||
listRecs( comms, "leaving rememberChannelAddress" );
|
||||
listRecs( comms, "leaving rememberChannelAddress()" );
|
||||
THREAD_CHECK_END();
|
||||
return rec;
|
||||
} /* rememberChannelAddress */
|
||||
|
|
|
@ -510,8 +510,9 @@ dvc_parseMQTTPacket( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* topic,
|
|||
stream_putBytes( stream, buf, len );
|
||||
|
||||
XP_U8 proto = 0;
|
||||
if ( stream_gotU8( stream, &proto )
|
||||
&& (proto == PROTO_1 || proto == PROTO_3 ) ) {
|
||||
if ( !stream_gotU8( stream, &proto ) ) {
|
||||
XP_LOGFF( "bad message: too short" );
|
||||
} else if ( proto == PROTO_1 || proto == PROTO_3 ) {
|
||||
MQTTDevID senderID;
|
||||
stream_getBytes( stream, &senderID, sizeof(senderID) );
|
||||
senderID = be64toh( senderID );
|
||||
|
|
|
@ -80,6 +80,24 @@ nliToGI( const NetLaunchInfo* nli, XWEnv xwe, XW_UtilCtxt* util, CurGameInfo* gi
|
|||
{
|
||||
gi_setNPlayers( gi, xwe, util, nli->nPlayersT, nli->nPlayersH );
|
||||
gi->gameID = nli->gameID;
|
||||
|
||||
XP_U16 nLocals = 0;
|
||||
XP_Bool remotesAreRobots = nli->remotesAreRobots;
|
||||
XW_DUtilCtxt* duc = util_getDevUtilCtxt( util, xwe );
|
||||
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
|
||||
LocalPlayer* lp = &gi->players[ii];
|
||||
if ( lp->isLocal ) {
|
||||
if ( nli->remotesAreRobots ) {
|
||||
lp->robotIQ = 1;
|
||||
}
|
||||
XP_UCHAR buf[64];
|
||||
XP_U16 len = VSIZE(buf);
|
||||
dutil_getUsername( duc, xwe, nLocals++, XP_TRUE, remotesAreRobots,
|
||||
buf, &len );
|
||||
replaceStringIfDifferent( util->mpool, &lp->name, buf );
|
||||
}
|
||||
}
|
||||
|
||||
/* These defaults can be overwritten when host starts game after all
|
||||
register */
|
||||
gi->boardSize = 15;
|
||||
|
|
|
@ -243,10 +243,9 @@ OBJ = \
|
|||
$(BUILD_PLAT_DIR)/relaycon.o \
|
||||
$(BUILD_PLAT_DIR)/mqttcon.o \
|
||||
$(BUILD_PLAT_DIR)/lindutil.o \
|
||||
$(BUILD_PLAT_DIR)/cmdspipe.o \
|
||||
$(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS)
|
||||
|
||||
LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG)
|
||||
LIBS = -lm -lpthread -luuid -lcurl -lcjson $(GPROFFLAG)
|
||||
ifdef USE_SQLITE
|
||||
LIBS += -lsqlite3
|
||||
LIBS += -lmosquitto
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
|
||||
/*
|
||||
* Copyright 2023 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 "cmdspipe.h"
|
||||
|
||||
XP_Bool
|
||||
cmds_readCmd( CmdBuf* cb, const char* buf )
|
||||
{
|
||||
XP_Bool success = XP_TRUE;
|
||||
XP_MEMSET( cb, 0, sizeof(*cb) );
|
||||
|
||||
XP_LOGFF( "got buf: %s", buf );
|
||||
|
||||
gchar** strs = g_strsplit ( buf, " ", 100 );
|
||||
if ( 0 == strcmp( strs[0], "null" ) ) {
|
||||
cb->cmd = CMD_NONE;
|
||||
} else if ( 0 == strcmp( strs[0], "quit" ) ) {
|
||||
cb->cmd = CMD_QUIT;
|
||||
} else {
|
||||
XP_ASSERT(0);
|
||||
success = XP_FALSE;
|
||||
}
|
||||
g_strfreev( strs );
|
||||
|
||||
return success;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef _CMDSPIPE_H_
|
||||
# define _CMDSPIPE_H_
|
||||
|
||||
#include "xptypes.h"
|
||||
|
||||
typedef enum { CMD_NONE,
|
||||
CMD_QUIT,
|
||||
} Cmd;
|
||||
|
||||
typedef struct _CmdBuf {
|
||||
Cmd cmd;
|
||||
} CmdBuf;
|
||||
|
||||
XP_Bool cmds_readCmd( CmdBuf* cb, const char* buf );
|
||||
|
||||
#endif
|
|
@ -78,7 +78,7 @@ static void
|
|||
addOne( CursGameList* cgl, sqlite3_int64 rowid )
|
||||
{
|
||||
GameInfo gib;
|
||||
if ( gdb_getGameInfo( cgl->params->pDb, rowid, &gib ) ) {
|
||||
if ( gdb_getGameInfoForRow( cgl->params->pDb, rowid, &gib ) ) {
|
||||
GameInfo* gibp = g_malloc( sizeof(*gibp) );
|
||||
*gibp = gib;
|
||||
cgl->games = g_slist_append( cgl->games, gibp );
|
||||
|
@ -121,7 +121,7 @@ cgl_refreshOne( CursGameList* cgl, sqlite3_int64 rowid, bool select )
|
|||
// elem
|
||||
|
||||
GameInfo gib;
|
||||
if ( gdb_getGameInfo( cgl->params->pDb, rowid, &gib ) ) {
|
||||
if ( gdb_getGameInfoForRow( cgl->params->pDb, rowid, &gib ) ) {
|
||||
GameInfo* found;
|
||||
GSList* elem = findFor( cgl, rowid );
|
||||
if ( !!elem ) {
|
||||
|
@ -289,6 +289,16 @@ cgl_getSel( CursGameList* cgl )
|
|||
return g_slist_nth_data( cgl->games, cgl->curSel );
|
||||
}
|
||||
|
||||
void
|
||||
cgl_setSel( CursGameList* cgl, int sel )
|
||||
{
|
||||
if ( sel < 0 ) {
|
||||
sel = XP_RANDOM() % g_slist_length( cgl->games );
|
||||
}
|
||||
cgl->curSel = sel;
|
||||
adjustCurSel( cgl );
|
||||
}
|
||||
|
||||
int
|
||||
cgl_getNGames( CursGameList* cgl )
|
||||
{
|
||||
|
|
|
@ -40,6 +40,7 @@ void cgl_moveSel( CursGameList* cgl, bool down );
|
|||
void cgl_draw( CursGameList* cgl );
|
||||
|
||||
const GameInfo* cgl_getSel( CursGameList* cgl );
|
||||
void cgl_setSel( CursGameList* cgl, int sel );
|
||||
int cgl_getNGames( CursGameList* cgl );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -192,14 +192,14 @@ cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, const cb_dims* dims )
|
|||
comms_resendAll( cGlobals->game.comms, NULL_XWE, COMMS_CONN_NONE, XP_FALSE );
|
||||
}
|
||||
if ( bGlobals->cGlobals.params->forceInvite ) {
|
||||
(void)ADD_ONETIME_IDLE( inviteIdle, bGlobals);
|
||||
(void)ADD_ONETIME_IDLE( inviteIdle, bGlobals );
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
cb_new( CursesBoardState* cbState, const cb_dims* dims )
|
||||
cb_new( CursesBoardState* cbState, const cb_dims* dims, const CurGameInfo* gi )
|
||||
{
|
||||
CursesBoardGlobals* bGlobals = findOrOpen( cbState, -1, NULL, NULL );
|
||||
CursesBoardGlobals* bGlobals = findOrOpen( cbState, -1, gi, NULL );
|
||||
if ( !!bGlobals ) {
|
||||
initMenus( bGlobals );
|
||||
enableDraw( bGlobals, dims );
|
||||
|
@ -217,8 +217,7 @@ cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
|
|||
CommsAddrRec selfAddr;
|
||||
makeSelfAddress( &selfAddr, params );
|
||||
|
||||
CursesBoardGlobals* bGlobals =
|
||||
commonInit( cbState, -1, NULL );
|
||||
CursesBoardGlobals* bGlobals = commonInit( cbState, -1, NULL );
|
||||
CommonGlobals* cGlobals = &bGlobals->cGlobals;
|
||||
initCP( cGlobals );
|
||||
if ( game_makeFromInvite( &cGlobals->game, NULL_XWE, nli, &selfAddr,
|
||||
|
@ -630,6 +629,7 @@ cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, XP_U16 expectSeed,
|
|||
bool success = 0 == expectSeed || seed == expectSeed;
|
||||
if ( success ) {
|
||||
gameGotBuf( cGlobals, XP_TRUE, buf, len, from );
|
||||
linuxSaveGame( &bGlobals->cGlobals );
|
||||
} else {
|
||||
XP_LOGFF( "msg for seed %d but I opened %d", expectSeed, seed );
|
||||
}
|
||||
|
@ -654,10 +654,35 @@ cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel,
|
||||
const CommsAddrRec* destAddr )
|
||||
{
|
||||
sqlite3_int64 rowids[1];
|
||||
int nRowIDs = VSIZE(rowids);
|
||||
gdb_getRowsForGameID( cbState->params->pDb, gameID, rowids, &nRowIDs );
|
||||
XP_ASSERT( 1 == nRowIDs );
|
||||
|
||||
CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowids[0], NULL, NULL );
|
||||
CommonGlobals* cGlobals = &bGlobals->cGlobals;
|
||||
CommsCtxt* comms = cGlobals->game.comms;
|
||||
|
||||
CommsAddrRec selfAddr;
|
||||
comms_getSelfAddr( comms, &selfAddr );
|
||||
|
||||
NetLaunchInfo nli;
|
||||
nli_init( &nli, cGlobals->gi, &selfAddr, 1, forceChannel );
|
||||
nli.remotesAreRobots = XP_TRUE;
|
||||
|
||||
comms_invite( comms, NULL_XWE, &nli, destAddr, XP_TRUE );
|
||||
linuxSaveGame( &bGlobals->cGlobals );
|
||||
}
|
||||
|
||||
static void
|
||||
kill_board( gpointer data )
|
||||
{
|
||||
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
|
||||
linuxSaveGame( &bGlobals->cGlobals );
|
||||
disposeBoard( bGlobals );
|
||||
}
|
||||
|
||||
|
@ -786,7 +811,7 @@ static void
|
|||
curses_util_turnChanged( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
|
||||
XP_S16 XP_UNUSED_DBG(newTurn) )
|
||||
{
|
||||
XP_LOGF( "%s(newTurn=%d)", __func__, newTurn );
|
||||
XP_LOGFF( "(newTurn=%d)", newTurn );
|
||||
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
|
||||
linuxSaveGame( &bGlobals->cGlobals );
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ CursesBoardState* cb_init( CursesAppGlobals* aGlobals, LaunchParams* params,
|
|||
void cb_resized( CursesBoardState* cbState, const cb_dims* dims );
|
||||
|
||||
void cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, const cb_dims* dims );
|
||||
bool cb_new( CursesBoardState* cbState, const cb_dims* dims );
|
||||
bool cb_new( CursesBoardState* cbState, const cb_dims* dims,
|
||||
const CurGameInfo* gi /* optional: use from globals if unset */ );
|
||||
void cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
|
||||
const cb_dims* dims );
|
||||
|
||||
|
@ -49,6 +50,8 @@ bool cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid,
|
|||
const CommsAddrRec* from );
|
||||
void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
|
||||
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from );
|
||||
void cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel,
|
||||
const CommsAddrRec* destAddr );
|
||||
void cb_closeAll( CursesBoardState* cbState );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <ctype.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <netdb.h> /* gethostbyname */
|
||||
#include <errno.h>
|
||||
|
@ -40,6 +42,8 @@
|
|||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include "linuxmain.h"
|
||||
#include "linuxutl.h"
|
||||
#include "linuxdict.h"
|
||||
|
@ -86,7 +90,6 @@ struct CursesAppGlobals {
|
|||
CursesMenuState* menuState;
|
||||
CursGameList* gameList;
|
||||
CursesBoardState* cbState;
|
||||
GSList* openGames;
|
||||
WINDOW* mainWin;
|
||||
int winWidth, winHeight;
|
||||
|
||||
|
@ -199,8 +202,8 @@ initCurses( CursesAppGlobals* aGlobals )
|
|||
keypad(stdscr, TRUE); /* effects wgetch only? */
|
||||
|
||||
getmaxyx( aGlobals->mainWin, aGlobals->winHeight, aGlobals->winWidth );
|
||||
XP_LOGF( "%s: getmaxyx()->w:%d; h:%d", __func__, aGlobals->winWidth,
|
||||
aGlobals->winHeight );
|
||||
XP_LOGFF( "getmaxyx()->w:%d; h:%d", aGlobals->winWidth,
|
||||
aGlobals->winHeight );
|
||||
}
|
||||
|
||||
/* globals->statusLine = height - MENU_WINDOW_HEIGHT - 1; */
|
||||
|
@ -312,7 +315,7 @@ handleNewGame( void* closure, int XP_UNUSED(key) )
|
|||
const CurGameInfo* gi = &aGlobals->cag.params->pgi;
|
||||
if ( !canMakeFromGI(gi) ) {
|
||||
ca_inform( aGlobals->mainWin, "Unable to create game (check params?)" );
|
||||
} else if ( !cb_new( aGlobals->cbState, &dims ) ) {
|
||||
} else if ( !cb_new( aGlobals->cbState, &dims, NULL ) ) {
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
return XP_TRUE;
|
||||
|
@ -624,7 +627,7 @@ handle_quitwrite( GIOChannel* source, GIOCondition XP_UNUSED(condition), gpointe
|
|||
static gboolean
|
||||
handle_winchwrite( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
|
||||
{
|
||||
XP_LOGF( "%s(condition=%x)", __func__, condition );
|
||||
XP_LOGFF( "(condition=%x)", condition );
|
||||
CursesAppGlobals* aGlobals = (CursesAppGlobals*)data;
|
||||
|
||||
/* Read from the pipe so it won't call again */
|
||||
|
@ -632,7 +635,7 @@ handle_winchwrite( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gp
|
|||
|
||||
struct winsize ws;
|
||||
ioctl( STDIN_FILENO, TIOCGWINSZ, &ws );
|
||||
XP_LOGF( "%s(): lines %d, columns %d", __func__, ws.ws_row, ws.ws_col );
|
||||
XP_LOGFF( "lines %d, columns %d", ws.ws_row, ws.ws_col );
|
||||
aGlobals->winHeight = ws.ws_row;
|
||||
aGlobals->winWidth = ws.ws_col;
|
||||
|
||||
|
@ -712,8 +715,8 @@ fireCursesTimer( CursesAppGlobals* globals )
|
|||
board_draw( globals->cGlobals.game.board );
|
||||
}
|
||||
} else {
|
||||
XP_LOGF( "skipping timer: now (%ld) < when (%ld)",
|
||||
now, smallestTip->when );
|
||||
XP_LOGFF( "skipping timer: now (%ld) < when (%ld)",
|
||||
now, smallestTip->when );
|
||||
}
|
||||
}
|
||||
} /* fireCursesTimer */
|
||||
|
@ -922,10 +925,9 @@ initClientSocket( CursesAppGlobals* globals, char* serverName )
|
|||
userError( globals, "unable to get host info for %s\n", serverName );
|
||||
} else {
|
||||
char* hostName = inet_ntoa( *(struct in_addr*)hostinfo->h_addr );
|
||||
XP_LOGF( "gethostbyname returned %s", hostName );
|
||||
XP_LOGFF( "gethostbyname returned %s", hostName );
|
||||
globals->csInfo.client.serverAddr = inet_addr(hostName);
|
||||
XP_LOGF( "inet_addr returned %lu",
|
||||
globals->csInfo.client.serverAddr );
|
||||
XP_LOGFF( "inet_addr returned %lu", globals->csInfo.client.serverAddr );
|
||||
}
|
||||
} /* initClientSocket */
|
||||
#endif
|
||||
|
@ -1298,7 +1300,7 @@ curses_requestMsgs( gpointer data )
|
|||
if ( '\0' != devIDBuf[0] ) {
|
||||
relaycon_requestMsgs( aGlobals->cag.params, devIDBuf );
|
||||
} else {
|
||||
XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ );
|
||||
XP_LOGFF( "not requesting messages as don't have relay id" );
|
||||
}
|
||||
return 0; /* don't run again */
|
||||
}
|
||||
|
@ -1330,7 +1332,7 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID,
|
|||
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
|
||||
sqlite3* pDb = aGlobals->cag.params->pDb;
|
||||
if ( !!devID ) {
|
||||
XP_LOGF( "%s(devID='%s')", __func__, devID );
|
||||
XP_LOGFF( "(devID='%s')", devID );
|
||||
|
||||
/* If we already have one, make sure it's the same! Else store. */
|
||||
gchar buf[64];
|
||||
|
@ -1357,7 +1359,7 @@ cursesErrorMsgRcvd( void* closure, const XP_UCHAR* msg )
|
|||
{
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
|
||||
if ( !!globals->lastErr && 0 == strcmp( globals->lastErr, msg ) ) {
|
||||
XP_LOGF( "skipping error message from relay" );
|
||||
XP_LOGFF( "skipping error message from relay" );
|
||||
} else {
|
||||
g_free( globals->lastErr );
|
||||
globals->lastErr = g_strdup( msg );
|
||||
|
@ -1445,6 +1447,254 @@ onGameSaved( CursesAppGlobals* aGlobals, sqlite3_int64 rowid, bool isNew )
|
|||
cgl_refreshOne( aGlobals->gameList, rowid, isNew );
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
makeGameFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
|
||||
{
|
||||
LaunchParams* params = aGlobals->cag.params;
|
||||
CurGameInfo gi = {0};
|
||||
gi_copy( MPPARM(params->mpool) &gi, ¶ms->pgi );
|
||||
gi.serverRole = SERVER_ISSERVER;
|
||||
gi.boardSize = 15;
|
||||
gi.traySize = 7;
|
||||
|
||||
cJSON* tmp = cJSON_GetObjectItem( args, "gid" );
|
||||
XP_ASSERT( !!tmp );
|
||||
sscanf( tmp->valuestring, "%X", &gi.gameID );
|
||||
|
||||
tmp = cJSON_GetObjectItem( args, "nPlayers" );
|
||||
XP_ASSERT( !!tmp );
|
||||
gi.nPlayers = tmp->valueint;
|
||||
|
||||
tmp = cJSON_GetObjectItem( args, "hostPosn" );
|
||||
XP_ASSERT( !!tmp );
|
||||
int hostPosn = tmp->valueint;
|
||||
replaceStringIfDifferent( params->mpool, &gi.players[hostPosn].name,
|
||||
params->localName );
|
||||
for ( int ii = 0; ii < gi.nPlayers; ++ii ) {
|
||||
gi.players[ii].isLocal = ii == hostPosn;
|
||||
gi.players[ii].robotIQ = 1;
|
||||
}
|
||||
|
||||
tmp = cJSON_GetObjectItem( args, "dict" );
|
||||
XP_ASSERT( tmp );
|
||||
replaceStringIfDifferent( params->mpool, &gi.dictName, tmp->valuestring );
|
||||
|
||||
cb_dims dims;
|
||||
figureDims( aGlobals, &dims );
|
||||
LOGGI( &gi, "prior to cb_new call" );
|
||||
bool success = cb_new( aGlobals->cbState, &dims, &gi );
|
||||
XP_ASSERT( success );
|
||||
|
||||
gi_disposePlayerInfo( MPPARM(params->mpool) &gi );
|
||||
return success;
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
inviteFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
|
||||
{
|
||||
/* char buf[1000]; */
|
||||
/* if ( cJSON_PrintPreallocated( args, buf, sizeof(buf), 0 ) ) { */
|
||||
/* XP_LOGFF( "(%s)", buf ); */
|
||||
/* } */
|
||||
|
||||
XP_U32 gameID;
|
||||
cJSON* tmp = cJSON_GetObjectItem( args, "gid" );
|
||||
XP_ASSERT( !!tmp );
|
||||
sscanf( tmp->valuestring, "%X", &gameID );
|
||||
|
||||
tmp = cJSON_GetObjectItem( args, "channel" );
|
||||
XP_ASSERT( !!tmp );
|
||||
XP_U16 channel = tmp->valueint;
|
||||
XP_LOGFF( "read channel: %X", channel );
|
||||
|
||||
/* CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel, */
|
||||
/* const CommsAddrRec* destAddr */
|
||||
CommsAddrRec destAddr = {0};
|
||||
cJSON* addr = cJSON_GetObjectItem( args, "addr" );
|
||||
XP_ASSERT( !!addr );
|
||||
tmp = cJSON_GetObjectItem( addr, "mqtt" );
|
||||
if ( !!tmp ) {
|
||||
XP_LOGFF( "parsing mqtt: %s", tmp->valuestring );
|
||||
addr_addType( &destAddr, COMMS_CONN_MQTT );
|
||||
XP_Bool success = strToMQTTCDevID( tmp->valuestring, &destAddr.u.mqtt.devID );
|
||||
XP_ASSERT( success );
|
||||
}
|
||||
tmp = cJSON_GetObjectItem( addr, "sms" );
|
||||
if ( !!tmp ) {
|
||||
XP_LOGFF( "parsing sms: %s", tmp->valuestring );
|
||||
addr_addType( &destAddr, COMMS_CONN_SMS );
|
||||
XP_STRCAT( destAddr.u.sms.phone, tmp->valuestring );
|
||||
destAddr.u.sms.port = 1;
|
||||
}
|
||||
|
||||
cb_addInvite( aGlobals->cbState, gameID, channel, &destAddr );
|
||||
LOG_RETURN_VOID();
|
||||
return XP_TRUE;
|
||||
}
|
||||
|
||||
static cJSON*
|
||||
getGamesStateForArgs( CursesAppGlobals* aGlobals, cJSON* args )
|
||||
{
|
||||
cJSON* result = cJSON_CreateArray();
|
||||
|
||||
LaunchParams* params = aGlobals->cag.params;
|
||||
cJSON* gids = cJSON_GetObjectItem(args, "gids" );
|
||||
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
|
||||
XP_U32 gameID;
|
||||
cJSON* gid = cJSON_GetArrayItem( gids, ii );
|
||||
sscanf( gid->valuestring, "%X", &gameID );
|
||||
|
||||
GameInfo gib;
|
||||
if ( gdb_getGameInfoForGID( params->pDb, gameID, &gib ) ) {
|
||||
cJSON* item = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject( item, "gid", gid->valuestring );
|
||||
cJSON_AddBoolToObject( item, "gameOver", gib.gameOver );
|
||||
cJSON_AddNumberToObject( item, "nPending", gib.nPending );
|
||||
cJSON_AddNumberToObject( item, "nMoves", gib.nMoves );
|
||||
cJSON_AddNumberToObject( item, "nTiles", gib.nTiles );
|
||||
|
||||
cJSON_AddItemToArray( result, item );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static cJSON*
|
||||
makeBoolObj( const char* key, XP_Bool value )
|
||||
{
|
||||
cJSON* result = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject( result, key, value );
|
||||
/* char buf[1000]; */
|
||||
/* if ( cJSON_PrintPreallocated( result, buf, sizeof(buf), 0 ) ) { */
|
||||
/* XP_LOGFF( "(%s=>%s)=>%s", key, boolToStr(value), buf ); */
|
||||
/* } */
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
on_incoming_signal( GSocketService* XP_UNUSED(service),
|
||||
GSocketConnection* connection,
|
||||
GObject* XP_UNUSED(source_object), gpointer user_data )
|
||||
{
|
||||
XP_LOGFF( "called" );
|
||||
CursesAppGlobals* aGlobals = (CursesAppGlobals*)user_data;
|
||||
LaunchParams* params = aGlobals->cag.params;
|
||||
|
||||
GInputStream* istream = g_io_stream_get_input_stream( G_IO_STREAM(connection) );
|
||||
|
||||
short len;
|
||||
gssize nread = g_input_stream_read( istream, &len, sizeof(len), NULL, NULL );
|
||||
XP_ASSERT( nread == sizeof(len) );
|
||||
len = ntohs(len);
|
||||
|
||||
gchar buf[len+1];
|
||||
nread = g_input_stream_read( istream, buf, len, NULL, NULL );
|
||||
if ( 0 <= nread ) {
|
||||
XP_ASSERT( nread == len );
|
||||
buf[nread] = '\0';
|
||||
XP_LOGFF( "Message was: \"%s\"\n", buf );
|
||||
|
||||
cJSON* reply = cJSON_CreateArray();
|
||||
|
||||
cJSON* cmds = cJSON_Parse( buf );
|
||||
XP_LOGFF( "got msg with array of len %d", cJSON_GetArraySize(cmds) );
|
||||
for ( int ii = 0 ; ii < cJSON_GetArraySize(cmds) ; ++ii ) {
|
||||
cJSON* item = cJSON_GetArrayItem( cmds, ii );
|
||||
cJSON* cmd = cJSON_GetObjectItem( item, "cmd" );
|
||||
cJSON* key = cJSON_GetObjectItem( item, "key" );
|
||||
cJSON* args = cJSON_GetObjectItem( item, "args" );
|
||||
const char* cmdStr = cmd->valuestring;
|
||||
|
||||
cJSON* response = NULL;
|
||||
|
||||
if ( 0 == strcmp( cmdStr, "quit" ) ) {
|
||||
response = getGamesStateForArgs( aGlobals, args );
|
||||
handleQuit( aGlobals, 0 );
|
||||
} else if ( 0 == strcmp( cmdStr, "getMQTTDevID" ) ) {
|
||||
MQTTDevID devID;
|
||||
dvc_getMQTTDevID( params->dutil, NULL_XWE, &devID );
|
||||
char buf[64];
|
||||
formatMQTTDevID( &devID, buf, sizeof(buf) );
|
||||
response = cJSON_CreateString( buf );
|
||||
} else if ( 0 == strcmp( cmdStr, "makeGame" ) ) {
|
||||
XP_Bool success = makeGameFromArgs( aGlobals, args );
|
||||
response = makeBoolObj( "success", success );
|
||||
} else if ( 0 == strcmp( cmdStr, "invite" ) ) {
|
||||
XP_Bool success = inviteFromArgs( aGlobals, args );
|
||||
response = makeBoolObj( "success", success );
|
||||
} else if ( 0 == strcmp( cmdStr, "gamesState" ) ) {
|
||||
response = getGamesStateForArgs( aGlobals, args );
|
||||
} else {
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
|
||||
XP_ASSERT( !!response );
|
||||
if ( !!response ) {
|
||||
cJSON* tmp = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject( tmp, "cmd", cmdStr );
|
||||
cJSON_AddNumberToObject( tmp, "key", key->valueint );
|
||||
cJSON_AddItemToObject( tmp, "response", response );
|
||||
|
||||
/*(void)*/cJSON_AddItemToArray( reply, tmp );
|
||||
}
|
||||
}
|
||||
cJSON_Delete( cmds ); /* this apparently takes care of all children */
|
||||
|
||||
char* replyStr = cJSON_PrintUnformatted( reply );
|
||||
short replyStrLen = strlen(replyStr);
|
||||
XP_LOGFF( "len(%s): %d", replyStr, replyStrLen );
|
||||
short replyStrNBOLen = htons(replyStrLen);
|
||||
|
||||
GOutputStream* ostream = g_io_stream_get_output_stream( G_IO_STREAM(connection) );
|
||||
gsize nwritten;
|
||||
gboolean wroteall = g_output_stream_write_all( ostream, &replyStrNBOLen, sizeof(replyStrNBOLen),
|
||||
&nwritten, NULL, NULL );
|
||||
XP_ASSERT( wroteall && nwritten == sizeof(replyStrNBOLen) );
|
||||
wroteall = g_output_stream_write_all( ostream, replyStr, replyStrLen, &nwritten, NULL, NULL );
|
||||
XP_ASSERT( wroteall && nwritten == replyStrLen );
|
||||
GError* error = NULL;
|
||||
g_output_stream_close( ostream, NULL, &error );
|
||||
if ( !!error ) {
|
||||
XP_LOGFF( "g_output_stream_close()=>%s", error->message );
|
||||
g_error_free( error );
|
||||
}
|
||||
cJSON_Delete( reply );
|
||||
free( replyStr );
|
||||
}
|
||||
|
||||
LOG_RETURN_VOID();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GSocketService*
|
||||
addCmdListener( CursesAppGlobals* aGlobals )
|
||||
{
|
||||
LOG_FUNC();
|
||||
LaunchParams* params = aGlobals->cag.params;
|
||||
const XP_UCHAR* cmdsSocket = params->cmdsSocket;
|
||||
GSocketService* service = NULL;
|
||||
if ( !!cmdsSocket ) {
|
||||
service = g_socket_service_new();
|
||||
|
||||
struct sockaddr_un addr = {0};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy( addr.sun_path, cmdsSocket, sizeof(addr.sun_path) - 1);
|
||||
GSocketAddress* gsaddr
|
||||
= g_socket_address_new_from_native (&addr, sizeof(addr) );
|
||||
GError* error = NULL;
|
||||
if ( g_socket_listener_add_address( (GSocketListener*)service, gsaddr, G_SOCKET_TYPE_STREAM,
|
||||
G_SOCKET_PROTOCOL_DEFAULT, NULL, NULL, &error ) ) {
|
||||
} else {
|
||||
XP_LOGFF( "g_socket_listener_add_address() failed: %s", error->message );
|
||||
}
|
||||
g_object_unref( gsaddr );
|
||||
|
||||
g_signal_connect( service, "incoming", G_CALLBACK(on_incoming_signal), aGlobals );
|
||||
}
|
||||
LOG_RETURNF( "%p", service );
|
||||
return service;
|
||||
}
|
||||
|
||||
void
|
||||
cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
|
||||
{
|
||||
|
@ -1468,6 +1718,8 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
|
|||
g_globals.gameList = cgl_init( params, g_globals.winWidth, params->cursesListWinHt );
|
||||
cgl_refresh( g_globals.gameList );
|
||||
|
||||
GSocketService* cmdService = addCmdListener( &g_globals );
|
||||
|
||||
// g_globals.amServer = isServer;
|
||||
/* g_globals.cGlobals.params = params; */
|
||||
/* #ifdef XWFEATURE_RELAY */
|
||||
|
@ -1566,14 +1818,17 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
|
|||
handleNewGame( &g_globals, 0 );
|
||||
}
|
||||
} else {
|
||||
/* Always open a game. Without that it won't attempt to connect and
|
||||
stalls are likely in the test script case at least. If that's
|
||||
annoying when running manually add a launch flag */
|
||||
/* Always open a game (at random). Without that it won't attempt to
|
||||
connect and stalls are likely in the test script case at least. If
|
||||
that's annoying when running manually add a launch flag */
|
||||
cgl_setSel( g_globals.gameList, -1 );
|
||||
handleOpenGame( &g_globals, 0 );
|
||||
}
|
||||
|
||||
g_main_loop_run( g_globals.loop );
|
||||
|
||||
g_object_unref( cmdService );
|
||||
|
||||
cb_closeAll( g_globals.cbState );
|
||||
|
||||
#ifdef XWFEATURE_BLUETOOTH
|
||||
|
|
|
@ -72,7 +72,6 @@ void cursesDrawCtxtFree( DrawCtx* dctx );
|
|||
* message.... Clearly there will need to be such a thing.
|
||||
*/
|
||||
|
||||
|
||||
void cursesmain( XP_Bool isServer, LaunchParams* params );
|
||||
bool handleQuit( void* closure, int unused_key );
|
||||
void inviteReceivedCurses( void* aGlobals, const NetLaunchInfo* invite );
|
||||
|
|
|
@ -550,12 +550,12 @@ gdb_getRelayIDsToRowsMap( sqlite3* pDb )
|
|||
}
|
||||
|
||||
XP_Bool
|
||||
gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
|
||||
gdb_getGameInfoForRow( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
|
||||
{
|
||||
XP_Bool success = XP_FALSE;
|
||||
const char* fmt = "SELECT ended, turn, local, nmoves, ntotal, nmissing, "
|
||||
"isoCode, seed, connvia, gameid, lastMoveTime, dupTimerExpires, "
|
||||
"relayid, scores, nPending, role, channel, created, snap "
|
||||
"relayid, scores, nPending, nTiles, role, channel, created, snap "
|
||||
"FROM games WHERE rowid = %lld";
|
||||
XP_UCHAR query[256];
|
||||
snprintf( query, sizeof(query), fmt, rowid );
|
||||
|
@ -587,6 +587,7 @@ gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
|
|||
len = sizeof(gib->scores);
|
||||
getColumnText( ppStmt, col++, gib->scores, &len );
|
||||
gib->nPending = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->nTiles = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->role = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->channelNo = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->created = sqlite3_column_int( ppStmt, col++ );
|
||||
|
@ -613,6 +614,16 @@ gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
|
|||
return success;
|
||||
}
|
||||
|
||||
XP_Bool
|
||||
gdb_getGameInfoForGID( sqlite3* pDb, XP_U32 gameID, GameInfo* gib )
|
||||
{
|
||||
sqlite3_int64 rowids[4];
|
||||
int nRowIDs = VSIZE(rowids);
|
||||
gdb_getRowsForGameID( pDb, gameID, rowids, &nRowIDs );
|
||||
return 1 == nRowIDs
|
||||
&& gdb_getGameInfoForRow( pDb, rowids[0], gib );
|
||||
}
|
||||
|
||||
void
|
||||
gdb_getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
|
||||
int* nRowIDs )
|
||||
|
|
|
@ -46,6 +46,7 @@ typedef struct _GameInfo {
|
|||
XP_S16 nMissing;
|
||||
XP_U16 seed;
|
||||
XP_U16 nPending;
|
||||
XP_U16 nTiles;
|
||||
XP_U32 lastMoveTime;
|
||||
XP_U32 dupTimerExpires;
|
||||
XP_U32 created;
|
||||
|
@ -69,7 +70,8 @@ void gdb_freeGamesList( GSList* games );
|
|||
/* Mapping of relayID -> rowid */
|
||||
GHashTable* gdb_getRelayIDsToRowsMap( sqlite3* pDb );
|
||||
|
||||
XP_Bool gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
|
||||
XP_Bool gdb_getGameInfoForRow( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
|
||||
XP_Bool gdb_getGameInfoForGID( sqlite3* pDb, XP_U32 gameID, GameInfo* gib );
|
||||
void gdb_getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
|
||||
int* nRowIDs );
|
||||
XP_Bool gdb_loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid );
|
||||
|
|
|
@ -39,6 +39,7 @@ typedef struct _WrapperState {
|
|||
} WrapperState;
|
||||
|
||||
static GSList* s_idleProcs = NULL;
|
||||
static pthread_mutex_t s_idleProcsMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void
|
||||
printElapsedFor( struct timespec* first, struct timespec* second,
|
||||
|
@ -81,7 +82,9 @@ idle_wrapper( gpointer data )
|
|||
XP_USE( procName );
|
||||
#endif
|
||||
if ( 0 == result ) { /* won't be getting called again */
|
||||
pthread_mutex_lock( &s_idleProcsMutex );
|
||||
s_idleProcs = g_slist_remove( s_idleProcs, state );
|
||||
pthread_mutex_unlock( &s_idleProcsMutex );
|
||||
g_free( state );
|
||||
}
|
||||
|
||||
|
@ -98,7 +101,9 @@ _wrapIdle( GSourceFunc function, gpointer data,
|
|||
/* XP_LOGF( TAG "%s(): installing proc %s from caller %s", __func__, */
|
||||
/* procName, caller ); */
|
||||
WrapperState* state = g_malloc0( sizeof(*state) );
|
||||
pthread_mutex_lock( &s_idleProcsMutex );
|
||||
s_idleProcs = g_slist_append( s_idleProcs, state );
|
||||
pthread_mutex_unlock( &s_idleProcsMutex );
|
||||
state->proc.srcProc = function;
|
||||
state->data = data;
|
||||
state->caller = caller;
|
||||
|
|
|
@ -87,8 +87,8 @@ enum { ROW_ITEM, ROW_THUMB, NAME_ITEM, CREATED_ITEM, GAMEID_ITEM,
|
|||
#ifdef XWFEATURE_RELAY
|
||||
RELAYID_ITEM,
|
||||
#endif
|
||||
OVER_ITEM, TURN_ITEM,LOCAL_ITEM, NMOVES_ITEM, NTOTAL_ITEM,
|
||||
MISSING_ITEM, LASTTURN_ITEM, DUPTIMER_ITEM,
|
||||
NPACKETS_ITEM, OVER_ITEM, TURN_ITEM,LOCAL_ITEM, NMOVES_ITEM,
|
||||
NTOTAL_ITEM, MISSING_ITEM, LASTTURN_ITEM, DUPTIMER_ITEM,
|
||||
|
||||
N_ITEMS,
|
||||
};
|
||||
|
@ -187,6 +187,7 @@ init_games_list( GtkAppGlobals* apg )
|
|||
#ifdef XWFEATURE_RELAY
|
||||
addTextColumn( list, "RelayID", RELAYID_ITEM );
|
||||
#endif
|
||||
addTextColumn( list, "nPackets", NPACKETS_ITEM );
|
||||
addTextColumn( list, "Ended", OVER_ITEM );
|
||||
addTextColumn( list, "Turn", TURN_ITEM );
|
||||
addTextColumn( list, "Local", LOCAL_ITEM );
|
||||
|
@ -210,6 +211,7 @@ init_games_list( GtkAppGlobals* apg )
|
|||
#ifdef XWFEATURE_RELAY
|
||||
G_TYPE_STRING, /*RELAYID_ITEM */
|
||||
#endif
|
||||
G_TYPE_INT, /* NPACKETS_ITEM */
|
||||
G_TYPE_BOOLEAN, /* OVER_ITEM */
|
||||
G_TYPE_INT, /* TURN_ITEM */
|
||||
G_TYPE_STRING, /* LOCAL_ITEM */
|
||||
|
@ -287,6 +289,7 @@ add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew,
|
|||
RELAYID_ITEM, gib->relayID,
|
||||
#endif
|
||||
TURN_ITEM, gib->turn,
|
||||
NPACKETS_ITEM, gib->nPending,
|
||||
OVER_ITEM, gib->gameOver,
|
||||
LOCAL_ITEM, localString,
|
||||
NMOVES_ITEM, gib->nMoves,
|
||||
|
@ -402,7 +405,7 @@ delete_game( GtkAppGlobals* apg, sqlite3_int64 rowid )
|
|||
#ifdef DEBUG
|
||||
XP_Bool success =
|
||||
#endif
|
||||
gdb_getGameInfo( params->pDb, rowid, &gib );
|
||||
gdb_getGameInfoForRow( params->pDb, rowid, &gib );
|
||||
XP_ASSERT( success );
|
||||
#ifdef XWFEATURE_RELAY
|
||||
XP_U32 clientToken = makeClientToken( rowid, gib.seed );
|
||||
|
@ -687,7 +690,7 @@ static void
|
|||
onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew )
|
||||
{
|
||||
GameInfo gib;
|
||||
if ( gdb_getGameInfo( apg->cag.params->pDb, rowid, &gib ) ) {
|
||||
if ( gdb_getGameInfoForRow( apg->cag.params->pDb, rowid, &gib ) ) {
|
||||
add_to_list( apg->listWidget, rowid, isNew, &gib );
|
||||
g_object_unref( gib.snap );
|
||||
}
|
||||
|
|
|
@ -68,12 +68,18 @@ static XP_UCHAR* linux_dutil_md5sum( XW_DUtilCtxt* duc, XWEnv xwe, const XP_U8*
|
|||
#endif
|
||||
|
||||
static void
|
||||
linux_dutil_getUsername( XW_DUtilCtxt* XP_UNUSED(duc), XWEnv XP_UNUSED(xwe),
|
||||
linux_dutil_getUsername( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
|
||||
XP_U16 num, XP_Bool XP_UNUSED(isLocal), XP_Bool isRobot,
|
||||
XP_UCHAR* buf, XP_U16* len )
|
||||
{
|
||||
const char* fmt = isRobot ? "Robot %d" : "Player %d";
|
||||
*len = XP_SNPRINTF( buf, *len, fmt, num );
|
||||
LaunchParams* params = (LaunchParams*)duc->closure;
|
||||
if ( params->localName ) {
|
||||
*len = XP_SNPRINTF( buf, *len, "%s", params->localName );
|
||||
XP_LOGFF( "set using local name: %s", buf );
|
||||
} else {
|
||||
const char* fmt = isRobot ? "Robot %d" : "Player %d";
|
||||
*len = XP_SNPRINTF( buf, *len, fmt, num );
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -101,7 +107,7 @@ linux_dutil_haveGame( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
|
|||
XP_Bool result = XP_FALSE;
|
||||
for ( int ii = 0; ii < nRowIDs; ++ii ) {
|
||||
GameInfo gib;
|
||||
if ( ! gdb_getGameInfo( pDb, rowids[ii], &gib ) ) {
|
||||
if ( ! gdb_getGameInfoForRow( pDb, rowids[ii], &gib ) ) {
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
if ( gib.channelNo == channel ) {
|
||||
|
@ -206,7 +212,7 @@ linux_dutil_ackMQTTMsg( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* topic,
|
|||
|
||||
res = curl_easy_perform(curl);
|
||||
XP_Bool success = res == CURLE_OK;
|
||||
XP_LOGFF( "curl_easy_perform() => %d", res );
|
||||
/* XP_LOGFF( "curl_easy_perform() => %d", res ); */
|
||||
if ( ! success ) {
|
||||
XP_LOGFF( "curl_easy_perform() failed: %s", curl_easy_strerror(res));
|
||||
}
|
||||
|
@ -216,7 +222,7 @@ linux_dutil_ackMQTTMsg( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* topic,
|
|||
curl_global_cleanup();
|
||||
g_free( json );
|
||||
|
||||
LOG_RETURN_VOID();
|
||||
/* LOG_RETURN_VOID(); */
|
||||
}
|
||||
|
||||
XW_DUtilCtxt*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
|
||||
/*
|
||||
* Copyright 2000 - 2020 by Eric House (xwords@eehouse.org). All rights
|
||||
* Copyright 2000 - 2023 by Eric House (xwords@eehouse.org). All rights
|
||||
* reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -75,7 +75,6 @@
|
|||
#include "dbgutil.h"
|
||||
#include "dictiter.h"
|
||||
#include "gsrcwrap.h"
|
||||
#include "cmdspipe.h"
|
||||
/* #include "commgr.h" */
|
||||
/* #include "compipe.h" */
|
||||
#include "memstream.h"
|
||||
|
@ -191,7 +190,7 @@ linuxOpenGame( CommonGlobals* cGlobals )
|
|||
cGlobals->gi,
|
||||
cGlobals->util, cGlobals->draw,
|
||||
&cGlobals->cp, &cGlobals->procs );
|
||||
XP_LOGFF( "loaded gi at %p", &cGlobals->gi );
|
||||
LOGGI( cGlobals->gi, __func__ );
|
||||
stream_destroy( stream );
|
||||
}
|
||||
|
||||
|
@ -293,7 +292,7 @@ void
|
|||
gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf,
|
||||
XP_U16 len, const CommsAddrRec* from )
|
||||
{
|
||||
XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw );
|
||||
XP_LOGFF( "(hasDraw=%d)", hasDraw );
|
||||
XP_Bool redraw = XP_FALSE;
|
||||
XWGame* game = &cGlobals->game;
|
||||
XWStreamCtxt* stream = stream_from_msgbuf( cGlobals, buf, len );
|
||||
|
@ -476,9 +475,9 @@ linuxSaveGame( CommonGlobals* cGlobals )
|
|||
if ( !!pDb ) {
|
||||
gdb_summarize( cGlobals );
|
||||
}
|
||||
XP_LOGF( "%s: saved", __func__ );
|
||||
XP_LOGFF( "saved" );
|
||||
} else {
|
||||
XP_LOGF( "%s: simulating save failure", __func__ );
|
||||
XP_LOGFF( "simulating save failure" );
|
||||
}
|
||||
}
|
||||
} /* linuxSaveGame */
|
||||
|
@ -715,6 +714,7 @@ typedef enum {
|
|||
,CMD_DROPNTHPACKET
|
||||
,CMD_NOHINTS
|
||||
,CMD_PICKTILESFACEUP
|
||||
,CMD_LOCALNAME
|
||||
,CMD_PLAYERNAME
|
||||
,CMD_REMOTEPLAYER
|
||||
,CMD_ROBOTNAME
|
||||
|
@ -869,6 +869,7 @@ static CmdInfoRec CmdInfoRecs[] = {
|
|||
"drop this packet; default 0 (none)" }
|
||||
,{ CMD_NOHINTS, false, "no-hints", "disallow hints" }
|
||||
,{ CMD_PICKTILESFACEUP, false, "pick-face-up", "allow to pick tiles" }
|
||||
,{ CMD_LOCALNAME, true, "localName", "name given all local players" }
|
||||
,{ CMD_PLAYERNAME, true, "name", "name of local, non-robot player" }
|
||||
,{ CMD_REMOTEPLAYER, false, "remote-player", "add an expected player" }
|
||||
,{ CMD_ROBOTNAME, true, "robot", "name of local, robot player" }
|
||||
|
@ -2555,57 +2556,6 @@ writeStatus( const char* statusSocket, const char* dbName )
|
|||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_gotcmd( GIOChannel* source, GIOCondition condition,
|
||||
gpointer data )
|
||||
{
|
||||
// XP_LOGFF( "got something!!" );
|
||||
gboolean keep = TRUE;
|
||||
|
||||
if ( 0 != (G_IO_IN & condition) ) {
|
||||
int sock = g_io_channel_unix_get_fd( source );
|
||||
char buf[1024];
|
||||
ssize_t nread = read( sock, buf, sizeof(buf) );
|
||||
buf[nread] = '\0';
|
||||
XP_LOGFF( "read: %s", buf );
|
||||
|
||||
LaunchParams* params = (LaunchParams*)data;
|
||||
|
||||
CmdBuf cb;
|
||||
cmds_readCmd( &cb, buf );
|
||||
switch( cb.cmd ) {
|
||||
case CMD_NONE:
|
||||
break;
|
||||
case CMD_QUIT:
|
||||
(*params->cmdProcs.quit)( params );
|
||||
break;
|
||||
default:
|
||||
XP_ASSERT( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 0 != ((G_IO_HUP) & condition) ) {
|
||||
XP_LOGFF( "got G_IO_HUP; returning FALSE" );
|
||||
keep = FALSE;
|
||||
} else if ( 0 != ((G_IO_ERR) & condition) ) {
|
||||
XP_LOGFF( "got G_IO_ERR; returning FALSE" );
|
||||
} else if ( 0 != ((G_IO_NVAL) & condition) ) {
|
||||
XP_LOGFF( "got G_IO_NVAL; returning FALSE" );
|
||||
} else {
|
||||
XP_LOGFF( "something else: 0x%X", condition );
|
||||
}
|
||||
return keep;
|
||||
}
|
||||
|
||||
static void
|
||||
addCmdListener( LaunchParams* params )
|
||||
{
|
||||
if ( !!params->cmdsSocket ) {
|
||||
int fifo = open( params->cmdsSocket, O_RDWR | O_NONBLOCK );
|
||||
ADD_SOCKET( params, fifo, handle_gotcmd );
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main( int argc, char** argv )
|
||||
{
|
||||
|
@ -2939,6 +2889,9 @@ main( int argc, char** argv )
|
|||
case CMD_PICKTILESFACEUP:
|
||||
mainParams.pgi.allowPickTiles = XP_TRUE;
|
||||
break;
|
||||
case CMD_LOCALNAME:
|
||||
mainParams.localName = optarg;
|
||||
break;
|
||||
case CMD_PLAYERNAME:
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
|
@ -3206,8 +3159,6 @@ main( int argc, char** argv )
|
|||
}
|
||||
}
|
||||
|
||||
addCmdListener( &mainParams );
|
||||
|
||||
/* add cur dir if dict search dir path is empty */
|
||||
if ( !mainParams.dictDirs ) {
|
||||
mainParams.dictDirs = g_slist_append( mainParams.dictDirs, "./" );
|
||||
|
|
|
@ -58,6 +58,7 @@ typedef struct _LaunchParams {
|
|||
GSList* dictDirs;
|
||||
char* fileName;
|
||||
char* dbName;
|
||||
char* localName;
|
||||
sqlite3* pDb; /* null unless opened */
|
||||
XP_U16 saveFailPct;
|
||||
XP_U16 smsSendFailPct;
|
||||
|
@ -302,7 +303,7 @@ struct CommonGlobals {
|
|||
|
||||
typedef struct _CommonAppGlobals {
|
||||
LaunchParams* params;
|
||||
GSList* globalsList;
|
||||
GSList* globalsList; /* used by gtk only */
|
||||
} CommonAppGlobals;
|
||||
|
||||
typedef struct _SourceData {
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os, subprocess, threading, time
|
||||
|
||||
gFIFO_NAME = '/tmp/fifo'
|
||||
|
||||
def launch_thread():
|
||||
print('launch_thread() called')
|
||||
args = ['./obj_linux_memdbg/xwords', '--curses', '--cmd-socket-name', gFIFO_NAME ]
|
||||
prcss = subprocess.Popen(args, stdout = subprocess.DEVNULL)
|
||||
print('launch_thread() calling communicate()')
|
||||
prcss.communicate()
|
||||
print('launch_thread() DONE')
|
||||
|
||||
def main():
|
||||
os.unlink(gFIFO_NAME)
|
||||
os.mkfifo(gFIFO_NAME)
|
||||
# mkfifo
|
||||
|
||||
# launch app in background
|
||||
thrd = threading.Thread( target=launch_thread)
|
||||
thrd.start()
|
||||
|
||||
# Loop writing to fifo
|
||||
for cmd in ['null', 'null', 'null', 'quit']:
|
||||
time.sleep(2)
|
||||
fifo_out = open( gFIFO_NAME, 'w' )
|
||||
print('open DONE')
|
||||
fifo_out.write( '{}'.format(cmd) )
|
||||
fifo_out.close()
|
||||
|
||||
# Kill app
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
535
xwords4/linux/scripts/netGamesTest.py
Executable file
535
xwords4/linux/scripts/netGamesTest.py
Executable file
|
@ -0,0 +1,535 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse, datetime, json, os, random, shutil, signal, \
|
||||
socket, struct, subprocess, sys, threading, time
|
||||
|
||||
g_NAMES = ['Brynn', 'Ariela', 'Kati', 'Eric']
|
||||
gDone = False
|
||||
|
||||
def log(args, msg):
|
||||
if args.VERBOSE:
|
||||
now = datetime.datetime.strftime(datetime.datetime.now(), '%X.%f')
|
||||
print('{} {}'.format(now, msg))
|
||||
|
||||
def pick_ndevs(args):
|
||||
RNUM = random.randint(0, 99)
|
||||
if RNUM > 95 and args.MAXDEVS >= 4:
|
||||
NDEVS = 4
|
||||
elif RNUM > 90 and args.MAXDEVS >= 3:
|
||||
NDEVS = 3
|
||||
else:
|
||||
NDEVS = 2
|
||||
if NDEVS < args.MINDEVS:
|
||||
NDEVS = args.MINDEVS
|
||||
return NDEVS
|
||||
|
||||
def chooseNames(nPlayers):
|
||||
players = g_NAMES[:]
|
||||
result = []
|
||||
for ii in range(nPlayers):
|
||||
indx = random.randint(0, len(players)-1)
|
||||
result.append(players.pop(indx))
|
||||
return result
|
||||
|
||||
class GuestGameInfo():
|
||||
def __init__(self, gid):
|
||||
self.gid = gid
|
||||
|
||||
# Should be subclass of GuestGameInfo
|
||||
class HostedInfo():
|
||||
def __init__(self, guests):
|
||||
self.guestNames = guests
|
||||
self.gid = '{:X}'.format(random.randint(0, 0x7FFFFFFF))
|
||||
self.invitesSent = False
|
||||
|
||||
def __str__(self):
|
||||
return 'gid: {}, guests: {}'.format(self.gid, self.guestNames)
|
||||
|
||||
class Device():
|
||||
_devs = {}
|
||||
_logdir = None
|
||||
|
||||
@staticmethod
|
||||
def setup():
|
||||
logdir = os.path.splitext(os.path.basename(sys.argv[0]))[0] + '_logs'
|
||||
Device._logdir = logdir
|
||||
# Move an existing logdir aside
|
||||
if os.path.exists(logdir):
|
||||
shutil.rmtree(logdir)
|
||||
# shutil.move(logdir, '/tmp/' + logdir + '_' + str(random.randint(0, 100000)))
|
||||
os.mkdir(logdir)
|
||||
for d in ['done', 'dead',]:
|
||||
os.mkdir(logdir + '/' + d)
|
||||
|
||||
def __init__(self, args, host):
|
||||
self.args = args
|
||||
self.endTime = None
|
||||
self.mqttDevID = None
|
||||
self.smsNumber = args.WITH_SMS and '{}_phone'.format(host) or None
|
||||
self.host = host
|
||||
self.hostedGames = [] # array of HostedInfo for each game I host
|
||||
self.guestGames = []
|
||||
self.script = '{}/{}.sh'.format(Device._logdir, host)
|
||||
self.dbName = '{}/{}.db'.format(Device._logdir, host)
|
||||
self.logfile = '{}/{}_log.txt'.format(Device._logdir, host)
|
||||
self.cmdSocketName = '{}/{}.sock'.format(Device._logdir, host)
|
||||
self.gameStates = {}
|
||||
self._keyCur = 10000 * (1 + g_NAMES.index(host))
|
||||
|
||||
def init(self):
|
||||
self._checkScript()
|
||||
|
||||
# called by thread proc
|
||||
def _launchProc(self):
|
||||
assert not self.endTime
|
||||
self.endTime = datetime.datetime.now() + datetime.timedelta(seconds = 5)
|
||||
args = [ self.script, '--close-stdin' ]
|
||||
if not self.args.USE_GTK: args.append('--curses')
|
||||
with open( self.logfile, 'a' ) as logfile:
|
||||
subprocess.run(args, stdout = subprocess.DEVNULL,
|
||||
stderr = logfile, universal_newlines = True)
|
||||
self._log('_launchProc() (in thread): subprocess FINISHED')
|
||||
os.unlink(self.cmdSocketName)
|
||||
self.endTime = None
|
||||
|
||||
def launchIfNot(self):
|
||||
if not self.endTime:
|
||||
self.watcher = threading.Thread(target = Device.runnerStub, args=(self,))
|
||||
self.watcher.isDaemon = True
|
||||
self.watcher.start()
|
||||
|
||||
while not self.endTime or not os.path.exists(self.cmdSocketName):
|
||||
time.sleep(0.2)
|
||||
|
||||
def _sendWaitReply(self, cmd, **kwargs):
|
||||
self.launchIfNot()
|
||||
|
||||
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
# print('connecting to: {}'.format(self.cmdSocketName))
|
||||
|
||||
client.connect(self.cmdSocketName);
|
||||
|
||||
key = self._nextKey()
|
||||
params = [{'cmd': cmd, 'key': key, 'args': {**kwargs}}]
|
||||
payload = json.dumps(params).encode()
|
||||
client.send(struct.pack('!h', len(payload)))
|
||||
client.sendall(payload)
|
||||
|
||||
# # Receive a response from the server
|
||||
# self._log('_sendWaitReply({}): calling recv()'.format(cmd))
|
||||
reslen = struct.unpack('!h', client.recv(2))[0]
|
||||
response = client.recv(reslen).decode()
|
||||
# self._log('_sendWaitReply({}): recv => str: {}'.format(cmd, response))
|
||||
response = json.loads(response)
|
||||
self._log('_sendWaitReply({}, {}): recv => {}'.format(cmd, kwargs, response))
|
||||
assert 1 == len(response)
|
||||
response = response[0]
|
||||
assert response.get('key', 0) == key
|
||||
assert response.get('cmd') == cmd
|
||||
response = response.get('response')
|
||||
|
||||
client.close()
|
||||
return response
|
||||
|
||||
def _nextKey(self):
|
||||
self._keyCur += 1
|
||||
return self._keyCur
|
||||
|
||||
def setDevID(self):
|
||||
response = self._sendWaitReply('getMQTTDevID')
|
||||
if response:
|
||||
self.mqttDevID = response
|
||||
|
||||
def makeGames(self):
|
||||
for remote in self.hostedGames:
|
||||
guests = remote.guestNames
|
||||
nPlayers = 1 + len(guests)
|
||||
hostPosn = random.randint(0, nPlayers-1)
|
||||
self._sendWaitReply('makeGame', nPlayers=nPlayers, hostPosn=hostPosn,
|
||||
gid=remote.gid, dict=self.args.DICTS[0])
|
||||
|
||||
# This is the heart of things. Do something as long as we have a
|
||||
# game that needs to run.
|
||||
def step(self):
|
||||
# self._log('step() called for {}'.format(self))
|
||||
stepped = False
|
||||
for game in self.hostedGames:
|
||||
if not game.invitesSent:
|
||||
self.invite(game)
|
||||
stepped = True
|
||||
break
|
||||
|
||||
if not stepped:
|
||||
if not self.endTime:
|
||||
self.launchIfNot()
|
||||
else:
|
||||
now = datetime.datetime.now()
|
||||
if now > self.endTime:
|
||||
self.quit()
|
||||
else:
|
||||
# self._log('sleeping with {} to go'.format(self.endTime-now))
|
||||
time.sleep(0.5)
|
||||
stepped = True;
|
||||
|
||||
def invite(self, game):
|
||||
failed = False
|
||||
for ii in range(len(game.guestNames)):
|
||||
guestName = game.guestNames[ii]
|
||||
# self._log('inviting {}'.format(guestName))
|
||||
guestDev = self._devs[guestName]
|
||||
|
||||
addr = {}
|
||||
if self.args.WITH_MQTT: addr['mqtt'] = guestDev.mqttDevID
|
||||
if self.args.WITH_SMS: addr['sms'] = guestDev.smsNumber
|
||||
response = self._sendWaitReply('invite', gid=game.gid,
|
||||
channel=ii+1, addr=addr,
|
||||
name=guestName) # just for logging
|
||||
|
||||
if response['success']:
|
||||
guestDev.expectInvite(game.gid)
|
||||
else:
|
||||
failed = True
|
||||
if not failed: game.invitesSent = True
|
||||
|
||||
def expectInvite(self, gid):
|
||||
self.guestGames.append(GuestGameInfo(gid))
|
||||
self.launchIfNot()
|
||||
|
||||
# Return true only if all games I host are finished on all games.
|
||||
# But: what about games I don't host? For now, let's make it all
|
||||
# games!
|
||||
def finished(self):
|
||||
allGames = self._allGames()
|
||||
result = 0 < len(allGames)
|
||||
for game in allGames:
|
||||
if not result: break
|
||||
peers = Device.devsWith(game.gid)
|
||||
for dev in peers:
|
||||
result = dev.gameOver(game.gid)
|
||||
if not result: break
|
||||
# if result: self._log('finished() => {}'.format(result))
|
||||
return result
|
||||
|
||||
def gameOver(self, gid):
|
||||
result = False
|
||||
"""Is the game is over for *this* device"""
|
||||
gameState = self.gameStates.get(gid, None)
|
||||
if gameState:
|
||||
result = gameState.get('gameOver', False) and 0 == gameState.get('nPending', 1)
|
||||
# if result: self._log('gameOver({}) => {}'.format(gid, result))
|
||||
return result
|
||||
|
||||
# this device is stalled if none of its unfinshed games has
|
||||
# changed state in some interval
|
||||
def stalled(self):
|
||||
return False
|
||||
|
||||
def _allGames(self):
|
||||
return self.hostedGames + self.guestGames
|
||||
|
||||
def haveGame(self, gid):
|
||||
withGid = [game for game in self._allGames() if gid == game.gid]
|
||||
return 0 < len(withGid)
|
||||
|
||||
def quit(self):
|
||||
if self.endTime:
|
||||
allGames = self._allGames()
|
||||
gids = [game.gid for game in allGames if not self.gameOver(game.gid)]
|
||||
response = self._sendWaitReply('quit', gids=gids)
|
||||
|
||||
for obj in response:
|
||||
gid = obj.get('gid')
|
||||
self.gameStates[gid] = obj
|
||||
|
||||
# wait for the thing to actually die
|
||||
self.watcher.join()
|
||||
self.watcher = None
|
||||
assert not self.endTime
|
||||
|
||||
def _checkScript(self):
|
||||
if not os.path.exists(self.script):
|
||||
scriptArgs = ['exec'] # without exec means terminate() won't work
|
||||
if self.args.VALGRIND:
|
||||
scriptArgs += ['valgrind']
|
||||
# args += ['--leak-check=full']
|
||||
# args += ['--track-origins=yes']
|
||||
scriptArgs.append(self.args.APP_NEW) # + [str(p) for p in self.params]
|
||||
|
||||
scriptArgs += '--db', self.dbName, '--skip-confirm'
|
||||
if self.args.SEND_CHAT:
|
||||
scriptArgs += '--send-chat', self.args.SEND_CHAT
|
||||
scriptArgs += '--localName', self.host
|
||||
scriptArgs += '--cmd-socket-name', self.cmdSocketName
|
||||
|
||||
if self.args.WITH_MQTT:
|
||||
scriptArgs += [ '--mqtt-port', self.args.MQTT_PORT, '--mqtt-host', self.args.MQTT_HOST ]
|
||||
|
||||
if self.args.WITH_SMS:
|
||||
scriptArgs += [ '--sms-number', self.smsNumber ]
|
||||
|
||||
scriptArgs += ['--board-size', '15', '--sort-tiles']
|
||||
|
||||
# useDupeMode = random.randint(0, 100) < self.args.DUP_PCT
|
||||
# if not useDupeMode: scriptArgs += ['--trade-pct', self.args.TRADE_PCT]
|
||||
|
||||
# if self.devID: args.extend( ' '.split(self.devID))
|
||||
scriptArgs += [ '$*' ]
|
||||
|
||||
with open( self.script, 'w' ) as fil:
|
||||
fil.write( "#!/bin/sh\n" )
|
||||
fil.write( ' '.join([str(arg) for arg in scriptArgs]) + '\n' )
|
||||
os.chmod(self.script, 0o755)
|
||||
|
||||
@staticmethod
|
||||
def deviceFor(args, host):
|
||||
dev = Device._devs.get(host)
|
||||
if not dev:
|
||||
dev = Device(args, host)
|
||||
Device._devs[host] = dev
|
||||
return dev
|
||||
|
||||
@staticmethod
|
||||
# return all devices (up to 4 of them) that are host or guest in a
|
||||
# game with <gid>"""
|
||||
def devsWith(gid):
|
||||
result = []
|
||||
for dev in Device._devs.values():
|
||||
if dev.haveGame(gid):
|
||||
result.append(dev)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def getAll():
|
||||
return [dev for dev in Device._devs.values()]
|
||||
|
||||
def addGameWith(self, guests):
|
||||
# self._log('addGameWith({})'.format(guests))
|
||||
hosted = HostedInfo(guests)
|
||||
self.hostedGames.append(hosted)
|
||||
for guest in guests:
|
||||
Device.deviceFor(self.args, guest) # in case this device never hosts
|
||||
|
||||
def _log(self, msg):
|
||||
log(self.args, '{}: {}'.format(self.host, msg))
|
||||
|
||||
def __str__(self):
|
||||
result = 'host: {}, devID: {}, with {} games: ' \
|
||||
.format(self.host, self.mqttDevID,
|
||||
len(self.hostedGames)+len(self.guestGames))
|
||||
result += '{' + ', '.join(['{}'.format(game) for game in self._allGames()]) + '}'
|
||||
result += ' running={}'.format(self.endTime is not None)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def runnerStub(self):
|
||||
self._launchProc()
|
||||
|
||||
def openOnExit(args):
|
||||
devs = Device.getAll()
|
||||
for dev in devs:
|
||||
appargs = [args.APP_NEW, '--db', dev.dbName]
|
||||
if args.WITH_MQTT:
|
||||
appargs += [ '--mqtt-port', args.MQTT_PORT, '--mqtt-host', args.MQTT_HOST ]
|
||||
subprocess.Popen([str(arg) for arg in appargs], stdout = subprocess.DEVNULL,
|
||||
stderr = subprocess.DEVNULL, universal_newlines = True)
|
||||
|
||||
def mainLoop(args, devs):
|
||||
startCount = len(devs)
|
||||
|
||||
startTime = datetime.datetime.now()
|
||||
nextStallCheck = startTime + datetime.timedelta(seconds = 20)
|
||||
|
||||
while 0 < len(devs):
|
||||
if gDone:
|
||||
print('gDone set; exiting loop')
|
||||
break
|
||||
dev = random.choice(devs)
|
||||
dev.step()
|
||||
if dev.finished():
|
||||
dev.quit()
|
||||
devs.remove(dev)
|
||||
log(args, 'removed dev for {}; {} devs left'.format(dev.host, len(devs)))
|
||||
# print('.', end='', flush=True)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
if devs and now > nextStallCheck:
|
||||
nextStallCheck = now + datetime.timedelta(seconds = 10)
|
||||
allStalled = True
|
||||
for dev in devs:
|
||||
if not dev.stalled():
|
||||
allStalled = False
|
||||
break
|
||||
if allStalled:
|
||||
log(args, 'exiting mainLoop with {} left (of {}) because all stalled' \
|
||||
.format(len(devs), startCount))
|
||||
break
|
||||
|
||||
if False and endTime < datetime.datetime.now():
|
||||
log(args, 'exiting mainLoop with {} left (of {}) because out of time' \
|
||||
.format(len(devs), startCount))
|
||||
break
|
||||
|
||||
for dev in devs:
|
||||
print('killing {}'.format(dev.host))
|
||||
dev.quit()
|
||||
|
||||
# We will build one Device for each player in the set of games, and
|
||||
# prime each with enough information that when we start running them
|
||||
# they can invite each other.
|
||||
def build_devs(args):
|
||||
for ii in range(args.NGAMES):
|
||||
nPlayers = pick_ndevs(args)
|
||||
players = chooseNames(nPlayers)
|
||||
host = players[0]
|
||||
guests = players[1:]
|
||||
|
||||
Device.deviceFor(args, host).addGameWith(guests)
|
||||
|
||||
return Device.getAll()
|
||||
|
||||
def mkParser():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--send-chat', dest = 'SEND_CHAT', type = str, default = None,
|
||||
help = 'the message to send')
|
||||
|
||||
parser.add_argument('--app-new', dest = 'APP_NEW', default = './obj_linux_memdbg/xwords',
|
||||
help = 'the app we\'ll use')
|
||||
# parser.add_argument('--app-old', dest = 'APP_OLD', default = './obj_linux_memdbg/xwords',
|
||||
# help = 'the app we\'ll upgrade from')
|
||||
# parser.add_argument('--start-pct', dest = 'START_PCT', default = 50, type = int,
|
||||
# help = 'odds of starting with the new app, 0 <= n < 100')
|
||||
# parser.add_argument('--upgrade-pct', dest = 'UPGRADE_PCT', default = 20, type = int,
|
||||
# help = 'odds of upgrading at any launch, 0 <= n < 100')
|
||||
|
||||
parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games')
|
||||
parser.add_argument('--timeout-mins', dest = 'TIMEOUT_MINS', default = 10000, type = int,
|
||||
help = 'minutes after which to timeout')
|
||||
# parser.add_argument('--nochange-secs', dest = 'NO_CHANGE_SECS', default = 30, type = int,
|
||||
# help = 'seconds without change after which to timeout')
|
||||
# parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go')
|
||||
# parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, action = 'store_true',
|
||||
# help = 'send all packet twice')
|
||||
# parser.add_argument('--phonies', dest = 'PHONIES', default = -1, type = int,
|
||||
# help = '0 (ignore), 1 (warn)) or 2 (lose turn); default is pick at random')
|
||||
# parser.add_argument('--make-phony-pct', dest = 'PHONY_PCT', default = 20, type = int,
|
||||
# help = 'how often a robot should play a phony (only applies when --phonies==2')
|
||||
parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true',
|
||||
help = 'run games using gtk instead of ncurses')
|
||||
|
||||
# parser.add_argument('--dup-pct', dest = 'DUP_PCT', default = 0, type = int,
|
||||
# help = 'this fraction played in duplicate mode')
|
||||
|
||||
# #
|
||||
# # echo " [--clean-start] \\" >&2
|
||||
parser.add_argument('--game-dict', dest = 'DICTS', action = 'append', default = [])
|
||||
# # echo " [--help] \\" >&2
|
||||
# # echo " [--max-devs <int>] \\" >&2
|
||||
parser.add_argument('--min-devs', dest = 'MINDEVS', type = int, default = 2,
|
||||
help = 'No game will have fewer devices than this')
|
||||
parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4,
|
||||
help = 'No game will have more devices than this')
|
||||
|
||||
# parser.add_argument('--robots-all-same-iq', dest = 'IQS_SAME', default = False,
|
||||
# action = 'store_true', help = 'give all robots the same IQ')
|
||||
|
||||
# parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2,
|
||||
# help = 'Keep each run alive at least this many seconds')
|
||||
# # echo " [--new-app <path/to/app] \\" >&2
|
||||
# # echo " [--new-app-args [arg*]] # passed only to new app \\" >&2
|
||||
# # echo " [--num-rooms <int>] \\" >&2
|
||||
# # echo " [--old-app <path/to/app]* \\" >&2
|
||||
# parser.add_argument('--one-per', dest = 'ONEPER', default = False,
|
||||
# action = 'store_true', help = 'force one player per device')
|
||||
# parser.add_argument('--resign-pct', dest = 'RESIGN_PCT', default = 0, type = int, \
|
||||
# help = 'Odds of resigning [0..100]')
|
||||
parser.add_argument('--seed', type = int, dest = 'SEED', default = 0)
|
||||
# # echo " [--send-chat <interval-in-seconds> \\" >&2
|
||||
# # echo " [--udp-incr <pct>] \\" >&2
|
||||
# # echo " [--udp-start <pct>] # default: $UDP_PCT_START \\" >&2
|
||||
# # echo " [--undo-pct <int>] \\" >&2
|
||||
|
||||
# parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int)
|
||||
# parser.add_argument('--trade-pct', dest = 'TRADE_PCT', default = 10, type = int)
|
||||
|
||||
parser.add_argument('--with-sms', dest = 'WITH_SMS', action = 'store_true')
|
||||
parser.add_argument('--without-sms', dest = 'WITH_SMS', default = False, action = 'store_false')
|
||||
# parser.add_argument('--sms-fail-pct', dest = 'SMS_FAIL_PCT', default = 0, type = int)
|
||||
|
||||
parser.add_argument('--with-mqtt', dest = 'WITH_MQTT', default = True, action = 'store_true')
|
||||
parser.add_argument('--without-mqtt', dest = 'WITH_MQTT', action = 'store_false')
|
||||
parser.add_argument('--mqtt-port', dest = 'MQTT_PORT', default = 1883 )
|
||||
parser.add_argument('--mqtt-host', dest = 'MQTT_HOST', default = 'localhost' )
|
||||
|
||||
# parser.add_argument('--force-tray', dest = 'TRAYSIZE', default = 0, type = int,
|
||||
# help = 'Always this many tiles per tray')
|
||||
|
||||
# parser.add_argument('--board-size', dest = 'BOARD_SIZE', type = int, default = 15,
|
||||
# help = 'Use <n>x<n> size board')
|
||||
# parser.add_argument('--rematch-limit-secs', dest = 'REMATCH_SECS', type = int, default = 0,
|
||||
# help = 'rematch games that end within this many seconds of script launch')
|
||||
|
||||
# envpat = 'DISCON_COREPAT'
|
||||
# parser.add_argument('--core-pat', dest = 'CORE_PAT', default = os.environ.get(envpat),
|
||||
# help = "pattern for core files that should stop the script " \
|
||||
# + "(default from env {})".format(envpat) )
|
||||
|
||||
parser.add_argument('--with-valgrind', dest = 'VALGRIND', default = False,
|
||||
action = 'store_true')
|
||||
|
||||
parser.add_argument('--debug', dest='VERBOSE', default=False, action='store_true',
|
||||
help='log stuff')
|
||||
parser.add_argument('--open-on-exit', dest = 'OPEN_ON_EXIT', default = False,
|
||||
action = 'store_true', help='Open devs in gtk app when finished')
|
||||
|
||||
return parser
|
||||
|
||||
def parseArgs():
|
||||
args = mkParser().parse_args()
|
||||
assignDefaults(args)
|
||||
print(args)
|
||||
return args
|
||||
# print(options)
|
||||
|
||||
def assignDefaults(args):
|
||||
if len(args.DICTS) == 0: args.DICTS.append('CollegeEng_2to8.xwd')
|
||||
args.LOGDIR = os.path.splitext(os.path.basename(sys.argv[0]))[0] + '_logs'
|
||||
# Move an existing logdir aside
|
||||
if os.path.exists(args.LOGDIR):
|
||||
shutil.move(args.LOGDIR, '/tmp/' + args.LOGDIR + '_' + str(random.randint(0, 100000)))
|
||||
for d in ['', 'done', 'dead',]:
|
||||
os.mkdir(args.LOGDIR + '/' + d)
|
||||
|
||||
def termHandler(signum, frame):
|
||||
global gDone
|
||||
print('termHandler() called')
|
||||
gDone = True
|
||||
|
||||
def main():
|
||||
startTime = datetime.datetime.now()
|
||||
signal.signal(signal.SIGINT, termHandler)
|
||||
|
||||
args = parseArgs()
|
||||
if args.SEED: random.seed(args.SEED)
|
||||
# Hack: old files confuse things. Remove is simple fix good for now
|
||||
if args.WITH_SMS:
|
||||
try: rmtree('/tmp/xw_sms')
|
||||
except: None
|
||||
|
||||
Device.setup()
|
||||
devs = build_devs(args)
|
||||
for dev in devs:
|
||||
dev.init()
|
||||
dev.launchIfNot()
|
||||
time.sleep(1)
|
||||
for dev in devs:
|
||||
if args.WITH_MQTT: dev.setDevID()
|
||||
for dev in devs:
|
||||
dev.makeGames()
|
||||
dev.quit()
|
||||
mainLoop(args, devs)
|
||||
|
||||
if args.OPEN_ON_EXIT: openOnExit(args)
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
34
xwords4/linux/scripts/opendevs.sh
Executable file
34
xwords4/linux/scripts/opendevs.sh
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
set -u -e
|
||||
|
||||
FILES=""
|
||||
ARGS=""
|
||||
APP=./obj_linux_memdbg/xwords
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 [args...] file1.db [file2..n.db]"
|
||||
echo "opens them with CrossWords, assuming they're dbs."
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ '--help' == $1 ]; then
|
||||
usage
|
||||
elif [ -f $1 ]; then
|
||||
if file -L $1 | grep -q 'SQLite 3.x database'; then
|
||||
FILES="${FILES} $1"
|
||||
else
|
||||
ARGS="${ARGS} $1"
|
||||
fi
|
||||
else
|
||||
ARGS="${ARGS} $1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
for FILE in $FILES; do
|
||||
LOGFILE="${FILE/.db/_log.txt}"
|
||||
echo >> $LOGFILE
|
||||
echo "******************** launch by $0 ********************" >> $LOGFILE
|
||||
exec ${APP} ${ARGS} --db $FILE 2>>$LOGFILE &
|
||||
done
|
|
@ -1,82 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e -u
|
||||
|
||||
IN_SEQ=''
|
||||
HTTP='--use-http'
|
||||
CURSES='--curses'
|
||||
SLEEP_SEC=10000
|
||||
|
||||
usage() {
|
||||
[ $# -gt 0 ] && echo "ERROR: $1"
|
||||
echo "usage: $0 --in-sequence|--at-once [--no-use-http] [--gtk]"
|
||||
cat <<EOF
|
||||
|
||||
Starts a pair of devices meant to get into the same game. Verification
|
||||
is by looking at the relay, usually with
|
||||
./relay/scripts/showinplay.sh. Both should have an 'A' in the ACK
|
||||
column.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--in-sequence)
|
||||
IN_SEQ=1
|
||||
;;
|
||||
--at-once)
|
||||
IN_SEQ=0
|
||||
;;
|
||||
--no-use-http)
|
||||
HTTP=''
|
||||
;;
|
||||
--gtk)
|
||||
CURSES=''
|
||||
;;
|
||||
*)
|
||||
usage "unexpected param $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ -n "$IN_SEQ" ] || usage "missing required param"
|
||||
|
||||
DB_TMPLATE=_cursesdb_
|
||||
LOG_TMPLATE=_curseslog_
|
||||
ROOM_TMPLATE=cursesRoom
|
||||
|
||||
echo "delete from msgs;" | psql xwgames
|
||||
echo "delete from games where room like '$ROOM_TMPLATE%';" | psql xwgames
|
||||
|
||||
rm -f ${DB_TMPLATE}*.sqldb
|
||||
rm -f ${LOG_TMPLATE}*
|
||||
|
||||
PIDS=''
|
||||
for GAME in $(seq 1); do
|
||||
ROOM=${ROOM_TMPLATE}${GAME}
|
||||
for N in $(seq 2); do
|
||||
# for N in $(seq 1); do
|
||||
DB=$DB_TMPLATE${GAME}_${N}.sqldb
|
||||
LOG=$LOG_TMPLATE${GAME}_${N}.log
|
||||
exec ./obj_linux_memdbg/xwords --server $CURSES --remote-player --robot Player \
|
||||
--room $ROOM --game-dict dict.xwd $HTTP\
|
||||
--skip-confirm --db $DB --close-stdin --server \
|
||||
>/dev/null 2>>$LOG &
|
||||
PID=$!
|
||||
echo "launched $PID"
|
||||
if [ $IN_SEQ -eq 1 ]; then
|
||||
sleep 9
|
||||
kill $PID
|
||||
sleep 1
|
||||
elif [ $IN_SEQ -eq 0 ]; then
|
||||
PIDS="$PIDS $PID"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
[ -n "${PIDS}" ] && sleep $SLEEP_SEC
|
||||
for PID in $PIDS; do
|
||||
kill $PID
|
||||
done
|
Loading…
Add table
Reference in a new issue