show host as part of each game display

This commit is contained in:
Eric House 2023-12-06 09:11:09 -08:00
parent 7b4f9ede41
commit 2936869b45
4 changed files with 200 additions and 48 deletions

View file

@ -603,7 +603,7 @@ enableDraw( CursesBoardGlobals* bGlobals, const cb_dims* dims )
setupBoard( bGlobals );
}
static CursesBoardGlobals*
CursesBoardGlobals*
findOrOpenForGameID( CursesBoardState* cbState, XP_U32 gameID,
const CurGameInfo* gi, const CommsAddrRec* returnAddr )
{
@ -617,6 +617,15 @@ findOrOpenForGameID( CursesBoardState* cbState, XP_U32 gameID,
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 )

View file

@ -56,6 +56,8 @@ void cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel
XP_Bool cb_makeRematch( CursesBoardState* cbState, XP_U32 gameID, XP_U32* newGameID );
XP_Bool cb_makeMoveIf( CursesBoardState* cbState, XP_U32 gameID );
const CommonGlobals* cb_getForGameID( CursesBoardState* cbState, XP_U32 gameID );
void cb_closeAll( CursesBoardState* cbState );
#endif

View file

@ -1472,13 +1472,18 @@ makeObjIfNot( cJSON** objp )
}
static void
addGIDToObject( cJSON** objp, XP_U32 gid, const char* key )
addStringToObject( cJSON** objp, const char* key, const char* value )
{
makeObjIfNot( objp );
cJSON_AddStringToObject( *objp, key, value );
}
static void
addGIDToObject( cJSON** objp, XP_U32 gid, const char* key )
{
char buf[16];
sprintf( buf, "%08X", gid );
cJSON_AddStringToObject( *objp, key, buf );
addStringToObject( objp, key, buf );
}
static void
@ -1603,14 +1608,18 @@ rematchFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
return result;
}
static cJSON*
getGamesStateForArgs( CursesAppGlobals* aGlobals, cJSON* args )
static XP_Bool
getGamesStateForArgs( CursesAppGlobals* aGlobals, cJSON* args,
cJSON** states, cJSON** orders )
{
cJSON* result = cJSON_CreateArray();
LOG_FUNC();
LaunchParams* params = aGlobals->cag.params;
cJSON* gids = cJSON_GetObjectItem(args, "gids" );
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
*states = cJSON_CreateArray();
cJSON* gids = cJSON_GetObjectItem( args, "gids" );
XP_Bool success = !!gids;
for ( int ii = 0 ; success && ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) );
GameInfo gib;
@ -1622,9 +1631,88 @@ getGamesStateForArgs( CursesAppGlobals* aGlobals, cJSON* args )
cJSON_AddNumberToObject( item, "nMoves", gib.nMoves );
cJSON_AddNumberToObject( item, "nTiles", gib.nTiles );
cJSON_AddItemToArray( result, item );
cJSON_AddItemToArray( *states, item );
}
}
XP_LOGFF( "done with states" ); /* got here */
if ( success && !!orders ) {
cJSON* gids = cJSON_GetObjectItem( args, "orders" );
if ( !gids ) {
*orders = NULL;
} else {
*orders = cJSON_CreateArray();
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) );
const CommonGlobals* cg = cb_getForGameID( aGlobals->cbState, gameID );
if ( !cg ) {
continue;
}
const XWGame* game = &cg->game;
if ( server_getGameIsConnected( game->server ) ) {
const CurGameInfo* gi = cg->gi;
LOGGI( gi, __func__ );
cJSON* order = NULL;
addGIDToObject( &order, gameID, "gid" );
cJSON* players = cJSON_CreateArray();
for ( int jj = 0; jj < gi->nPlayers; ++jj ) {
XP_LOGFF( "looking at player %d", jj );
const LocalPlayer* lp = &gi->players[jj];
XP_LOGFF( "adding player %d: %s", jj, lp->name );
cJSON* cName = cJSON_CreateString( lp->name );
cJSON_AddItemToArray( players, cName);
}
cJSON_AddItemToObject( order, "players", players );
cJSON_AddItemToArray( *orders, order );
}
}
}
}
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}
/* Return for each gid and array of player names, in play order, and including
for each whether it's the host and if a robot. For now let's try by opening
each game (yeah! yuck!) to read the info directly. Later add to a the db
accessed by gamesdb.c
*/
static cJSON*
getPlayersForArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
cJSON* result = cJSON_CreateArray();
cJSON* gids = cJSON_GetObjectItem( args, "gids" );
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) );
const CommonGlobals* cg = cb_getForGameID( aGlobals->cbState, gameID );
const CurGameInfo* gi = cg->gi;
LOGGI( gi, __func__ );
const XWGame* game = &cg->game;
cJSON* players = cJSON_CreateArray();
for ( int jj = 0; jj < gi->nPlayers; ++jj ) {
cJSON* playerObj = NULL;
const LocalPlayer* lp = &gi->players[jj];
XP_LOGFF( "adding player %d: %s", jj, lp->name );
addStringToObject( &playerObj, "name", lp->name );
XP_Bool isLocal = lp->isLocal;
cJSON_AddBoolToObject( playerObj, "isLocal", isLocal );
/* Roles: I don't think a guest in a 3- or 4-device game knows
which of the other players is host. Host is who it sends its
moves to, but is there an order there? */
XP_Bool isHost = game_getIsHost( game );
isHost = isHost && isLocal;
cJSON_AddBoolToObject( playerObj, "isHost", isHost );
cJSON_AddItemToArray( players, playerObj );
}
cJSON_AddItemToArray( result, players );
}
return result;
}
@ -1649,7 +1737,7 @@ on_incoming_signal( GSocketService* XP_UNUSED(service),
if ( 0 <= nread ) {
XP_ASSERT( nread == len );
buf[nread] = '\0';
XP_LOGFF( "Message was: \"%s\"\n", buf );
XP_LOGFF( "Message: \"%s\"\n", buf );
cJSON* reply = cJSON_CreateArray();
@ -1666,8 +1754,10 @@ on_incoming_signal( GSocketService* XP_UNUSED(service),
XP_Bool success = XP_TRUE;
if ( 0 == strcmp( cmdStr, "quit" ) ) {
cJSON* gids = getGamesStateForArgs( aGlobals, args );
addObjectToObject( &response, "states", gids );
cJSON* gids;
if ( getGamesStateForArgs( aGlobals, args, &gids, NULL ) ) {
addObjectToObject( &response, "states", gids );
}
handleQuit( aGlobals, 0 );
} else if ( 0 == strcmp( cmdStr, "getMQTTDevID" ) ) {
MQTTDevID devID;
@ -1693,8 +1783,16 @@ on_incoming_signal( GSocketService* XP_UNUSED(service),
addGIDToObject( &response, newGameID, "newGid" );
}
} else if ( 0 == strcmp( cmdStr, "getStates" ) ) {
cJSON* gids = getGamesStateForArgs( aGlobals, args );
addObjectToObject( &response, "states", gids );
cJSON* gids;
cJSON* orders;
success = getGamesStateForArgs( aGlobals, args, &gids, &orders );
if ( success ) {
addObjectToObject( &response, "states", gids );
addObjectToObject( &response, "orders", orders );
}
} else if ( 0 == strcmp( cmdStr, "getPlayers" ) ) {
cJSON* players = getPlayersForArgs( aGlobals, args );
addObjectToObject( &response, "players", players );
} else {
success = XP_FALSE;
XP_ASSERT(0);

View file

@ -38,7 +38,8 @@ def chooseNames(nPlayers):
return result
class GameInfo():
def __init__(self, gid, rematchLevel):
def __init__(self, device, gid, rematchLevel):
self.device = device
self.gid = gid
self.state = {}
assert isinstance(rematchLevel, int)
@ -52,34 +53,50 @@ class GameInfo():
def gameOver(self):
return self.state.get('gameOver', False)
def getDevice(self): return self.device
class GuestGameInfo(GameInfo):
def __init__(self, gid, rematchLevel):
super().__init__(gid, rematchLevel)
def __init__(self, device, gid, rematchLevel):
super().__init__(device, gid, rematchLevel)
class HostGameInfo(GameInfo):
def __init__(self, guestNames, **kwargs):
super().__init__(kwargs.get('gid'), kwargs.get('rematchLevel'))
def __init__(self, device, guestNames, **kwargs):
super().__init__(device, kwargs.get('gid'), kwargs.get('rematchLevel'))
self.guestNames = guestNames
self.needsInvite = kwargs.get('needsInvite', True)
self.orderedPlayers = None
global gGamesMade;
gGamesMade += 1
def haveOrder(self): return self.orderedPlayers is not None
def setOrder(self, orderedPlayers): self.orderedPlayers = orderedPlayers
def __str__(self):
return 'gid: {}, guests: {}'.format(self.gid, self.guestNames)
class GameStatus():
_N_LINES = 3 # could be lower if all games have 2 players
_statuses = None
def __init__(self, gid):
self.gid = gid
self.players = []
self.allOver = True
self.hostPlayerName = None
self.hostName = None
def harvest(self, dev):
self.players.append(dev.host)
self.allOver = self.allOver and dev.gameOver(self.gid)
def sortPlayers(self):
game = Device.getHostGame(self.gid)
orderedPlayers = game.orderedPlayers
if orderedPlayers:
assert len(orderedPlayers) == len(self.players)
self.players = orderedPlayers
self.hostName = game.getDevice().host
# Build a gid->status map for each game, querying each device in
# the game for details
@staticmethod
@ -95,11 +112,18 @@ class GameStatus():
for gid in list(statuses.keys()):
if statuses[gid].allOver: del statuses[gid]
for status in statuses.values():
status.sortPlayers()
GameStatus._statuses = statuses
@staticmethod
def numLines():
return GameStatus._N_LINES
maxPlayers = 0
for status in GameStatus._statuses.values():
nPlayers = len(status.players)
if nPlayers > maxPlayers: maxPlayers = nPlayers
return maxPlayers + 1
# For all games, print the proper line of status for that game in
# exactly 8 chars
@ -109,25 +133,22 @@ class GameStatus():
for gid in sorted(GameStatus._statuses.keys()):
if indx == 0:
results.append(gid)
continue
players = indx == 1 and g_NAMES[:2] or g_NAMES[2:]
lineTxt = ''
for player in players:
if not player in GameStatus._statuses[gid].players:
lineTxt += ' '
else:
dev = Device._devs.get(player)
initial = player[0]
arg2 = -1
gameState = dev.gameFor(gid).state
if gameState:
if gameState.get('gameOver', False):
initial = initial.lower()
arg2 = gameState.get('nPending', 0)
else:
arg2 = gameState.get('nTiles')
lineTxt += '{}{: 3}'.format(initial, arg2)
results.append(lineTxt)
elif indx <= len(GameStatus._statuses[gid].players):
status = GameStatus._statuses[gid]
player = status.players[indx-1]
hostMarker = status.hostName == player and '*' or ' '
initial = player[0]
arg3 = -1
dev = Device._devs.get(player)
gameState = dev.gameFor(gid).state
if gameState:
if gameState.get('gameOver', False):
initial = initial.lower()
arg3 = gameState.get('nPending', 0)
else:
arg3 = gameState.get('nTiles')
results.append('{}{}{: 3}'.format(hostMarker, initial, arg3).center(len(gid)))
return ' '.join(results)
class Device():
@ -279,15 +300,15 @@ class Device():
rematchLevel = game.rematchLevel - 1
assert rematchLevel >= 0 # fired
self.hostedGames.append(HostGameInfo(guests, needsInvite=False, gid=newGid,
self.hostedGames.append(HostGameInfo(self, guests, needsInvite=False, gid=newGid,
rematchLevel=rematchLevel))
for guest in guests:
Device.getFor(guest).expectInvite(newGid, rematchLevel)
Device.getForPlayer(guest).expectInvite(newGid, rematchLevel)
def invite(self, game):
failed = False
for ii in range(len(game.guestNames)):
guestDev = Device.getFor(game.guestNames[ii])
guestDev = Device.getForPlayer(game.guestNames[ii])
addr = {}
if self.args.WITH_MQTT: addr['mqtt'] = guestDev.mqttDevID
@ -302,7 +323,7 @@ class Device():
if not failed: game.needsInvite = False
def expectInvite(self, gid, rematchLevel):
self.guestGames.append(GuestGameInfo(gid, rematchLevel))
self.guestGames.append(GuestGameInfo(self, gid, rematchLevel))
self.launchIfNot()
# Return true only if all games I host are finished on all games.
@ -349,7 +370,20 @@ class Device():
def rematchOrQuit(self):
if self.endTime:
gids = [game.gid for game in self._allGames() if not self.gameOver(game.gid)]
response = self._sendWaitReply('getStates', gids=gids)
orders = []
for gid in gids:
game = Device.getHostGame(gid)
if game and not game.haveOrder():
orders.append(gid)
response = self._sendWaitReply('getStates', gids=gids, orders=orders)
for order in response.get('orders', []):
gid = order.get('gid')
game = Device.getHostGame(gid)
assert isinstance(game, HostGameInfo) # firing
game.setOrder(order.get('players'))
anyRematched = False
for obj in response.get('states', []):
@ -449,7 +483,7 @@ class Device():
return [dev for dev in Device._devs.values()]
@staticmethod
def getFor(player):
def getForPlayer(player):
result = None
for dev in Device.getAll():
if dev.host == player:
@ -458,8 +492,17 @@ class Device():
assert result
return result
@staticmethod
def getHostGame(gid):
devs = Device.devsWith(gid)
for dev in devs:
game = dev.gameFor(gid)
if isinstance(game, HostGameInfo):
return game
assert False
def addGameWith(self, guests):
self.hostedGames.append(HostGameInfo(guests, needsInvite=True,
self.hostedGames.append(HostGameInfo(self, guests, needsInvite=True,
rematchLevel=self.args.REMATCH_LEVEL))
for guest in guests:
Device.deviceFor(self.args, guest) # in case this device never hosts