xwords/xwords4/common/game.c
ehouse 4c8cf98d24 Add and set a compile-time option so comms will set a periodic timer
and use it to send and check for heartbeats over any transport.
Caller must supply a reset proc which is called when heartbeat hasn't
been received in too long.  No changes required to comms protocol, but
that means the heartbeat interval is fixed at compile time: can't be
negotiated, and the two ends had better agree.  Currently tested with
linux host and PalmOS guest, where only the first heartbeat failure is
recovered from.  So there's some debugging to be done still.
2007-11-18 23:43:27 +00:00

486 lines
15 KiB
C

/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */
/*
* Copyright 2001 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 "game.h"
#include "dictnry.h"
#include "strutils.h"
#ifdef CPLUS
extern "C" {
#endif
#ifdef DEBUG
static void
assertUtilOK( XW_UtilCtxt* util )
{
UtilVtable* vtable = util->vtable;
XP_U16 nSlots = sizeof(vtable) / 4;
while ( nSlots-- ) {
void* fptr = ((void**)vtable)[nSlots];
XP_ASSERT( !!fptr );
}
} /* assertUtilOK */
#else
# define assertUtilOK(u)
#endif
static void
checkServerRole( CurGameInfo* gi, XP_U16* nPlayersHere, XP_U16* nPlayersTotal )
{
if ( !!gi ) {
XP_Bool standAlone = gi->serverRole == SERVER_STANDALONE;
XP_U16 i, remoteCount = 0;
for ( i = 0; i < gi->nPlayers; ++i ) {
LocalPlayer* player = &gi->players[i];
if ( !player->isLocal ) {
++remoteCount;
if ( standAlone ) {
player->isLocal = XP_TRUE;
}
}
}
if ( remoteCount == 0 && gi->serverRole != SERVER_ISCLIENT ) {
gi->serverRole = SERVER_STANDALONE;
}
*nPlayersHere = gi->nPlayers - remoteCount;
if ( gi->serverRole == SERVER_ISCLIENT ) {
*nPlayersTotal = 0;
} else {
*nPlayersTotal = gi->nPlayers;
}
}
} /* checkServerRole */
void
game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi,
XW_UtilCtxt* util, DrawCtx* draw,
XP_U16 gameID, CommonPrefs* cp,
TransportSend sendproc, IF_CH( TransportReset resetproc )
void* closure )
{
XP_U16 nPlayersHere, nPlayersTotal;
assertUtilOK( util );
checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
gi->gameID = gameID;
game->model = model_make( MPPARM(mpool) (DictionaryCtxt*)NULL, util,
gi->boardSize, gi->boardSize );
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!sendproc && gi->serverRole != SERVER_STANDALONE ) {
game->comms = comms_make( MPPARM(mpool) util,
gi->serverRole != SERVER_ISCLIENT,
nPlayersHere, nPlayersTotal,
sendproc, IF_CH(resetproc) closure );
} else {
game->comms = (CommsCtxt*)NULL;
}
#endif
game->server = server_make( MPPARM(mpool) game->model,
#ifndef XWFEATURE_STANDALONE_ONLY
game->comms,
#else
(CommsCtxt*)NULL,
#endif
util );
game->board = board_make( MPPARM(mpool) game->model, game->server,
draw, util );
server_prefsChanged( game->server, cp );
board_prefsChanged( game->board, cp );
} /* game_makeNewGame */
void
game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util,
XP_U16 gameID, CommonPrefs* cp, TransportSend sendproc,
IF_CH(TransportReset resetproc) void* closure )
{
XP_U16 i;
XP_U16 nPlayersHere, nPlayersTotal;
XP_ASSERT( !!game->model );
XP_ASSERT( !!gi );
checkServerRole( gi, &nPlayersHere, &nPlayersTotal );
gi->gameID = gameID;
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!game->comms ) {
if ( gi->serverRole == SERVER_STANDALONE ) {
comms_destroy( game->comms );
game->comms = NULL;
} else {
comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT,
nPlayersHere, nPlayersTotal );
}
} else if ( gi->serverRole != SERVER_STANDALONE ) {
game->comms = comms_make( MPPARM(mpool) util,
gi->serverRole != SERVER_ISCLIENT,
nPlayersHere, nPlayersTotal,
sendproc, IF_CH(resetproc) closure );
}
#endif
model_init( game->model, gi->boardSize, gi->boardSize );
server_reset( game->server,
#ifndef XWFEATURE_STANDALONE_ONLY
game->comms
#else
NULL
#endif
);
board_reset( game->board );
for ( i = 0; i < gi->nPlayers; ++i ) {
gi->players[i].secondsUsed = 0;
}
server_prefsChanged( game->server, cp );
board_prefsChanged( game->board, cp );
} /* game_reset */
XP_Bool
game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game,
CurGameInfo* gi, DictionaryCtxt* dict,
XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp,
TransportSend sendProc, IF_CH(TransportReset resetProc)
void* closure )
{
XP_Bool success = XP_FALSE;
XP_U8 strVersion;
#ifndef XWFEATURE_STANDALONE_ONLY
XP_Bool hasComms;
#endif
strVersion = stream_getU8( stream );
XP_DEBUGF( "strVersion = %d", (XP_U16)strVersion );
if ( strVersion <= CUR_STREAM_VERS ) {
stream_setVersion( stream, strVersion );
gi_readFromStream( MPPARM(mpool) stream, gi );
#ifndef XWFEATURE_STANDALONE_ONLY
hasComms = stream_getU8( stream );
if ( hasComms ) {
game->comms = comms_makeFromStream( MPPARM(mpool) stream, util,
sendProc, IF_CH(resetProc) closure );
} else {
game->comms = NULL;
}
#endif
game->model = model_makeFromStream( MPPARM(mpool) stream, dict, util );
game->server = server_makeFromStream( MPPARM(mpool) stream,
game->model,
#ifndef XWFEATURE_STANDALONE_ONLY
game->comms,
#else
(CommsCtxt*)NULL,
#endif
util, gi->nPlayers );
game->board = board_makeFromStream( MPPARM(mpool) stream, game->model,
game->server, draw, util,
gi->nPlayers );
server_prefsChanged( game->server, cp );
board_prefsChanged( game->board, cp );
success = XP_TRUE;
} else {
XP_LOGF( "%s: aborting; stream version too new!", __FUNCTION__ );
}
return success;
} /* game_makeFromStream */
void
game_saveToStream( const XWGame* game, const CurGameInfo* gi,
XWStreamCtxt* stream )
{
stream_putU8( stream, CUR_STREAM_VERS );
gi_writeToStream( stream, gi );
#ifndef XWFEATURE_STANDALONE_ONLY
stream_putU8( stream, (XP_U8)!!game->comms );
if ( !!game->comms ) {
comms_writeToStream( game->comms, stream );
}
#endif
model_writeToStream( game->model, stream );
server_writeToStream( game->server, stream );
board_writeToStream( game->board, stream );
} /* game_saveToStream */
void
game_dispose( XWGame* game )
{
/* The board should be reused!!! PENDING(ehouse) */
if ( !!game->board ) {
board_destroy( game->board );
}
#ifndef XWFEATURE_STANDALONE_ONLY
if ( !!game->comms ) {
comms_destroy( game->comms );
}
#endif
if ( !!game->model ) {
DictionaryCtxt* dict = model_getDictionary( game->model );
if ( !!dict ) {
dict_destroy( dict );
}
model_destroy( game->model );
}
if ( !!game->server ) {
server_destroy( game->server );
}
} /* game_dispose */
void
gi_initPlayerInfo( MPFORMAL CurGameInfo* gi, const XP_UCHAR* nameTemplate )
{
XP_U16 i;
XP_MEMSET( gi, 0, sizeof(*gi) );
gi->nPlayers = 2;
gi->boardSize = 15;
gi->robotSmartness = SMART_ROBOT;
gi->gameSeconds = 25 * 60; /* 25 minute game is common? */
for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) {
XP_UCHAR buf[20];
LocalPlayer* fp = &gi->players[i];
if ( !!nameTemplate ) {
XP_SNPRINTF( buf, sizeof(buf), nameTemplate, i+1 );
XP_ASSERT( fp->name == NULL );
fp->name = copyString( mpool, buf );
}
fp->isRobot = (i == 0); /* one robot */
fp->isLocal = XP_TRUE;
fp->secondsUsed = 0;
}
} /* game_initPlayerInfo */
static void
disposePlayerInfoInt( MPFORMAL CurGameInfo* gi )
{
XP_U16 i;
LocalPlayer* lp;
for ( lp = gi->players, i = 0; i < MAX_NUM_PLAYERS; ++lp, ++i ) {
if ( !!lp->name ) {
XP_FREE( mpool, lp->name );
lp->name = (XP_UCHAR*)NULL;
}
if ( !!lp->password ) {
XP_FREE( mpool, lp->password );
lp->password = (XP_UCHAR*)NULL;
}
}
} /* disposePlayerInfoInt */
void
gi_disposePlayerInfo( MPFORMAL CurGameInfo* gi )
{
disposePlayerInfoInt( MPPARM(mpool) gi );
if ( !!gi->dictName ) {
XP_FREE( mpool, gi->dictName );
gi->dictName = (XP_UCHAR*)NULL;
}
} /* gi_disposePlayerInfo */
void
gi_copy( MPFORMAL CurGameInfo* destGI, CurGameInfo* srcGI )
{
XP_U16 nPlayers, i;
LocalPlayer* srcPl;
LocalPlayer* destPl;
replaceStringIfDifferent( mpool, &destGI->dictName,
srcGI->dictName );
destGI->gameID = srcGI->gameID;
destGI->gameSeconds = srcGI->gameSeconds;
destGI->nPlayers = (XP_U8)srcGI->nPlayers;
nPlayers = srcGI->nPlayers;
destGI->boardSize = (XP_U8)srcGI->boardSize;
destGI->serverRole = srcGI->serverRole;
destGI->hintsNotAllowed = srcGI->hintsNotAllowed;
destGI->timerEnabled = srcGI->timerEnabled;
destGI->robotSmartness = (XP_U8)srcGI->robotSmartness;
destGI->phoniesAction = srcGI->phoniesAction;
destGI->allowPickTiles = srcGI->allowPickTiles;
for ( srcPl = srcGI->players, destPl = destGI->players, i = 0;
i < nPlayers; ++srcPl, ++destPl, ++i ) {
replaceStringIfDifferent( mpool, &destPl->name, srcPl->name );
replaceStringIfDifferent( mpool, &destPl->password,
srcPl->password );
destPl->secondsUsed = srcPl->secondsUsed;
destPl->isRobot = srcPl->isRobot;
destPl->isLocal = srcPl->isLocal;
}
} /* gi_copy */
XP_U16
gi_countLocalHumans( const CurGameInfo* gi )
{
XP_U16 count = 0;
XP_U16 nPlayers = gi->nPlayers;
const LocalPlayer* lp = gi->players;
while ( nPlayers-- ) {
if ( lp->isLocal && !lp->isRobot ) {
++count;
}
++lp;
}
return count;
} /* gi_countLocalHumans */
void
gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi )
{
LocalPlayer* pl;
XP_U16 i;
XP_UCHAR* str;
XP_U16 strVersion = stream_getVersion( stream );
str = stringFromStream( mpool, stream );
replaceStringIfDifferent( mpool, &gi->dictName, str );
if ( !!str ) {
XP_FREE( mpool, str );
}
gi->nPlayers = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS );
gi->boardSize = (XP_U8)stream_getBits( stream, 4 );
gi->serverRole = (DeviceRole)stream_getBits( stream, 2 );
gi->hintsNotAllowed = stream_getBits( stream, 1 );
gi->robotSmartness = (XP_U8)stream_getBits( stream, 2 );
gi->phoniesAction = (XWPhoniesChoice)stream_getBits( stream, 2 );
gi->timerEnabled = stream_getBits( stream, 1 );
if ( strVersion >= STREAM_VERS_41B4 ) {
gi->allowPickTiles = stream_getBits( stream, 1 );
gi->allowHintRect = stream_getBits( stream, 1 );
} else {
gi->allowPickTiles = XP_FALSE;
gi->allowHintRect = XP_FALSE;
}
gi->gameID = stream_getU16( stream );
if ( gi->timerEnabled ) {
gi->gameSeconds = stream_getU16( stream );
}
for ( pl = gi->players, i = 0; i < gi->nPlayers; ++pl, ++i ) {
str = stringFromStream( mpool, stream );
replaceStringIfDifferent( mpool, &pl->name, str );
if ( !!str ) {
XP_FREE( mpool, str );
}
str = stringFromStream( mpool, stream );
replaceStringIfDifferent( mpool, &pl->password, str );
if ( !!str ) {
XP_FREE( mpool, str );
}
pl->secondsUsed = stream_getU16( stream );
pl->isRobot = stream_getBits( stream, 1 );
pl->isLocal = stream_getBits( stream, 1 );
}
} /* gi_readFromStream */
void
gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi )
{
const LocalPlayer* pl;
XP_U16 i;
stringToStream( stream, gi->dictName );
stream_putBits( stream, NPLAYERS_NBITS, gi->nPlayers );
stream_putBits( stream, 4, gi->boardSize );
stream_putBits( stream, 2, gi->serverRole );
stream_putBits( stream, 1, gi->hintsNotAllowed );
stream_putBits( stream, 2, gi->robotSmartness );
stream_putBits( stream, 2, gi->phoniesAction );
stream_putBits( stream, 1, gi->timerEnabled );
stream_putBits( stream, 1, gi->allowPickTiles );
stream_putBits( stream, 1, gi->allowHintRect );
stream_putU16( stream, gi->gameID );
if ( gi->timerEnabled) {
stream_putU16( stream, gi->gameSeconds );
}
for ( pl = gi->players, i = 0; i < gi->nPlayers; ++pl, ++i ) {
stringToStream( stream, pl->name );
stringToStream( stream, pl->password );
stream_putU16( stream, pl->secondsUsed );
stream_putBits( stream, 1, pl->isRobot );
stream_putBits( stream, 1, pl->isLocal );
}
} /* gi_writeToStream */
XP_Bool
player_hasPasswd( LocalPlayer* player )
{
XP_UCHAR* password = player->password;
/* XP_ASSERT( player->isLocal ); */
return !!password && *password != '\0';
} /* player_hasPasswd */
XP_Bool
player_passwordMatches( LocalPlayer* player, XP_U8* buf, XP_U16 len )
{
XP_ASSERT( player->isLocal );
return (XP_STRLEN(player->password) == len)
&& (0 == XP_STRNCMP( player->password, (XP_UCHAR*)buf, len ));
} /* player_passwordMatches */
XP_U16
player_timePenalty( CurGameInfo* gi, XP_U16 playerNum )
{
XP_S16 seconds = (gi->gameSeconds / gi->nPlayers);
LocalPlayer* player = gi->players + playerNum;
XP_U16 result = 0;
seconds -= player->secondsUsed;
if ( seconds < 0 ) {
seconds = -seconds;
seconds += 59;
result = (seconds/60) * 10;
}
return result;
} /* player_timePenalty */
#ifdef CPLUS
}
#endif