xwords/xwords4/common/nwgamest.c
2011-04-12 22:17:45 -07:00

597 lines
18 KiB
C

/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE"; -*- */
/*
* Copyright 1997 - 2009 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 "nwgamest.h"
#include "strutils.h"
#include "LocalizedStrIncludes.h"
#ifdef CPLUS
extern "C" {
#endif
#define NG_NUM_COLS ((XP_U16)(NG_COL_PASSWD+1))
struct NewGameCtx {
NewGameEnableColProc enableColProc;
NewGameEnableAttrProc enableAttrProc;
NewGameSetColProc setColProc;
NewGameGetColProc getColProc;
NewGameSetAttrProc setAttrProc;
XW_UtilCtxt* util;
void* closure;
/* Palm needs to store cleartext passwords separately in order to
store '***' in the visible field */
XP_TriEnable enabled[NG_NUM_COLS][MAX_NUM_PLAYERS];
XP_U16 nPlayersShown; /* real nPlayers lives in gi */
XP_U16 nPlayersTotal; /* used only until changedNPlayers set */
XP_U16 nLocalPlayers; /* not changed except in newg_load */
DeviceRole role;
XP_Bool isNewGame;
XP_Bool changedNPlayers;
XP_TriEnable juggleEnabled;
#ifndef XWFEATURE_STANDALONE_ONLY
XP_TriEnable settingsEnabled;
#endif
MPSLOT
};
static void enableOne( NewGameCtx* ngc, XP_U16 player, NewGameColumn col,
XP_TriEnable enable, XP_Bool force );
static void adjustAllRows( NewGameCtx* ngc, XP_Bool force );
static void adjustOneRow( NewGameCtx* ngc, XP_U16 player, XP_Bool force );
static void setRoleStrings( NewGameCtx* ngc );
static void considerEnable( NewGameCtx* ngc );
static void storePlayer( NewGameCtx* ngc, XP_U16 player, LocalPlayer* lp );
static void loadPlayer( NewGameCtx* ngc, XP_U16 player,
const LocalPlayer* lp );
#ifndef XWFEATURE_STANDALONE_ONLY
static XP_Bool changeRole( NewGameCtx* ngc, DeviceRole role );
static XP_Bool checkConsistent( NewGameCtx* ngc, XP_Bool warnUser );
#else
# define checkConsistent( ngc, warnUser ) XP_TRUE
#endif
NewGameCtx*
newg_make( MPFORMAL XP_Bool isNewGame,
XW_UtilCtxt* util,
NewGameEnableColProc enableColProc,
NewGameEnableAttrProc enableAttrProc,
NewGameGetColProc getColProc, NewGameSetColProc setColProc,
NewGameSetAttrProc setAttrProc, void* closure )
{
NewGameCtx* result = XP_MALLOC( mpool, sizeof(*result) );
XP_MEMSET( result, 0, sizeof(*result) );
result->enableColProc = enableColProc;
result->enableAttrProc = enableAttrProc;
result->setColProc = setColProc;
result->getColProc = getColProc;
result->setAttrProc = setAttrProc;
result->closure = closure;
result->isNewGame = isNewGame;
result->util = util;
MPASSIGN(result->mpool, mpool);
return result;
} /* newg_make */
void
newg_destroy( NewGameCtx* ngc )
{
XP_FREE( ngc->mpool, ngc );
} /* newg_destroy */
void
newg_load( NewGameCtx* ngc, const CurGameInfo* gi )
{
void* closure = ngc->closure;
NGValue value;
XP_U16 nPlayers, nLoaded;
XP_S16 ii, jj;
DeviceRole role;
XP_Bool localOnly;
XP_Bool shown[MAX_NUM_PLAYERS] = { XP_FALSE };
ngc->juggleEnabled = TRI_ENAB_NONE;
ngc->settingsEnabled = TRI_ENAB_NONE;
for ( ii = 0; ii < NG_NUM_COLS; ++ii ) {
for ( jj = 0; jj < MAX_NUM_PLAYERS; ++jj ) {
ngc->enabled[ii][jj] = TRI_ENAB_NONE;
}
}
ngc->role = role = gi->serverRole;
localOnly = (role == SERVER_ISCLIENT) && ngc->isNewGame;
#ifndef XWFEATURE_STANDALONE_ONLY
value.ng_role = role;
(*ngc->setAttrProc)( closure, NG_ATTR_ROLE, value );
(*ngc->enableAttrProc)( closure, NG_ATTR_ROLE, ngc->isNewGame?
TRI_ENAB_ENABLED : TRI_ENAB_DISABLED );
#endif
nPlayers = gi->nPlayers;
ngc->nPlayersTotal = nPlayers;
#ifndef XWFEATURE_STANDALONE_ONLY
for ( ii = nPlayers - 1; ii >= 0; --ii ) {
if ( gi->players[ii].isLocal ) {
++ngc->nLocalPlayers;
}
}
#endif
if ( localOnly ) {
nPlayers = ngc->nLocalPlayers;
}
ngc->nPlayersShown = nPlayers;
value.ng_u16 = ngc->nPlayersShown;
(*ngc->setAttrProc)( closure, NG_ATTR_NPLAYERS, value );
(*ngc->enableAttrProc)( closure, NG_ATTR_NPLAYERS, ngc->isNewGame?
TRI_ENAB_ENABLED : TRI_ENAB_DISABLED );
setRoleStrings( ngc );
considerEnable( ngc );
/* Load local players first */
nLoaded = 0;
do {
for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) {
if ( !shown[ii] ) {
const LocalPlayer* lp = &gi->players[ii];
if ( !localOnly
|| (lp->isLocal && (nLoaded < ngc->nLocalPlayers)) ) {
shown[ii] = XP_TRUE;
loadPlayer( ngc, nLoaded++, lp );
}
}
}
XP_ASSERT( localOnly || nLoaded == MAX_NUM_PLAYERS );
localOnly = XP_FALSE; /* for second pass */
} while ( nLoaded < MAX_NUM_PLAYERS );
adjustAllRows( ngc, XP_TRUE );
} /* newg_load */
typedef struct NGCopyClosure {
XP_U16 player;
NewGameColumn col;
NewGameCtx* ngc;
LocalPlayer* lp;
} NGCopyClosure;
static void
cpToLP( NGValue value, const void* cbClosure )
{
NGCopyClosure* cpcl = (NGCopyClosure*)cbClosure;
LocalPlayer* lp = cpcl->lp;
XP_UCHAR** strAddr = NULL;
switch ( cpcl->col ) {
#ifndef XWFEATURE_STANDALONE_ONLY
case NG_COL_REMOTE:
lp->isLocal = !value.ng_bool;
break;
#endif
case NG_COL_NAME:
strAddr = &lp->name;
break;
case NG_COL_PASSWD:
strAddr = &lp->password;
break;
case NG_COL_ROBOT:
lp->robotIQ = value.ng_bool ? 1 : 0;
break;
}
if ( !!strAddr ) {
/* This is leaking!!! But doesn't leak if am playing via IR, at least
in the simulator. */
replaceStringIfDifferent( cpcl->ngc->mpool, strAddr,
value.ng_cp );
}
} /* cpToLP */
XP_Bool
newg_store( NewGameCtx* ngc, CurGameInfo* gi,
XP_Bool XP_UNUSED_STANDALONE(warn) )
{
XP_U16 player;
XP_Bool consistent = checkConsistent( ngc, warn );
if ( consistent ) {
XP_Bool makeLocal = XP_FALSE;
gi->nPlayers = ngc->nPlayersShown;
#ifndef XWFEATURE_STANDALONE_ONLY
gi->serverRole = ngc->role;
makeLocal = ngc->role != SERVER_ISSERVER;
#endif
for ( player = 0; player < MAX_NUM_PLAYERS; ++player ) {
storePlayer( ngc, player, &gi->players[player] );
if ( makeLocal ) {
gi->players[player].isLocal = XP_TRUE;
}
}
}
return consistent;
} /* newg_store */
void
newg_colChanged( NewGameCtx* ngc, XP_U16 player )
{
/* Sometimes we'll get this notification for inactive rows, e.g. when
setting default values. */
if ( player < ngc->nPlayersShown ) {
adjustOneRow( ngc, player, XP_FALSE );
}
}
void
newg_attrChanged( NewGameCtx* ngc, NewGameAttr attr, NGValue value )
{
XP_Bool changed = XP_FALSE;
if ( attr == NG_ATTR_NPLAYERS ) {
if ( ngc->nPlayersShown != value.ng_u16 ) {
ngc->nPlayersShown = value.ng_u16;
ngc->changedNPlayers = XP_TRUE;
changed = XP_TRUE;
}
#ifndef XWFEATURE_STANDALONE_ONLY
} else if ( NG_ATTR_ROLE == attr ) {
changed = changeRole( ngc, value.ng_role );
#endif
} else {
XP_ASSERT( 0 );
}
if ( changed ) {
considerEnable( ngc );
adjustAllRows( ngc, XP_FALSE );
}
}
typedef struct DeepValue {
NGValue value;
NewGameColumn col;
MPSLOT
} DeepValue;
static void
deepCopy( NGValue value, const void* closure )
{
DeepValue* dvp = (DeepValue*)closure;
switch ( dvp->col ) {
case NG_COL_ROBOT:
#ifndef XWFEATURE_STANDALONE_ONLY
case NG_COL_REMOTE:
#endif
dvp->value.ng_bool = value.ng_bool;
break;
case NG_COL_NAME:
case NG_COL_PASSWD:
dvp->value.ng_cp = copyString( dvp->mpool, value.ng_cp );
break;
}
}
XP_Bool
newg_juggle( NewGameCtx* ngc )
{
XP_Bool changed = XP_FALSE;
XP_U16 nPlayers = ngc->nPlayersShown;
XP_ASSERT( ngc->isNewGame );
if ( nPlayers > 1 ) {
LocalPlayer tmpPlayers[MAX_NUM_PLAYERS];
XP_U16 pos[MAX_NUM_PLAYERS];
XP_U16 player;
/* Get a randomly juggled array of numbers 0..nPlayers-1. Then the
number at pos[n] inicates where the entry currently at n should
be. */
changed = randIntArray( pos, nPlayers );
if ( changed ) {
/* Deep-copy off to tmp storage. But skip lines that won't be moved
in the juggle. */
XP_MEMSET( &tmpPlayers, 0, sizeof(tmpPlayers) );
for ( player = 0; player < nPlayers; ++player ) {
if ( player != pos[player] ) {
storePlayer( ngc, player, &tmpPlayers[player] );
}
}
for ( player = 0; player < nPlayers; ++player ) {
if ( player != pos[player] ) {
LocalPlayer* lp = &tmpPlayers[player];
XP_U16 dest = pos[player];
loadPlayer( ngc, dest, lp );
XP_FREEP( ngc->mpool, &lp->name );
XP_FREEP( ngc->mpool, &lp->password );
XP_FREEP( ngc->mpool, &lp->dictName );
adjustOneRow( ngc, dest, XP_FALSE );
}
}
}
}
return changed;
} /* newg_juggle */
#ifndef XWFEATURE_STANDALONE_ONLY
static XP_Bool
checkConsistent( NewGameCtx* ngc, XP_Bool warnUser )
{
XP_Bool consistent;
XP_U16 i;
/* If ISSERVER, make sure there's at least one non-local player. */
consistent = ngc->role != SERVER_ISSERVER;
for ( i = 0; !consistent && i < ngc->nPlayersShown; ++i ) {
DeepValue dValue;
dValue.col = NG_COL_REMOTE;
(*ngc->getColProc)( ngc->closure, i, NG_COL_REMOTE,
deepCopy, &dValue );
if ( dValue.value.ng_bool ) {
consistent = XP_TRUE;
}
}
if ( !consistent && warnUser ) {
util_userError( ngc->util, ERR_REG_SERVER_SANS_REMOTE );
}
/* Add other consistency checks, and error messages, here. */
return consistent;
} /* checkConsistent */
#endif
static void
enableOne( NewGameCtx* ngc, XP_U16 player, NewGameColumn col,
XP_TriEnable enable, XP_Bool force )
{
XP_TriEnable* esp = &ngc->enabled[col][player];
if ( force || (*esp != enable) ) {
(*ngc->enableColProc)( ngc->closure, player, col, enable );
}
*esp = enable;
} /* enableOne */
static void
adjustAllRows( NewGameCtx* ngc, XP_Bool force )
{
XP_U16 player;
for ( player = 0; player < MAX_NUM_PLAYERS; ++player ) {
adjustOneRow( ngc, player, force );
}
} /* adjustAllRows */
static void
adjustOneRow( NewGameCtx* ngc, XP_U16 player, XP_Bool force )
{
XP_TriEnable enable[NG_NUM_COLS];
NewGameColumn col;
XP_Bool isLocal = XP_TRUE;
XP_Bool isNewGame = ngc->isNewGame;
DeviceRole role = ngc->role;
DeepValue dValue;
for ( col = 0; col < NG_NUM_COLS; ++col ) {
enable[col] = TRI_ENAB_HIDDEN;
}
/* If there aren't this many players, all are disabled */
if ( player >= ngc->nPlayersShown ) {
/* do nothing: all are hidden above */
} else {
#ifndef XWFEATURE_STANDALONE_ONLY
/* If standalone or client, remote is hidden. If server but not
new game, it's disabled */
if ( (role == SERVER_ISSERVER )
|| (role == SERVER_ISCLIENT && !isNewGame ) ) {
if ( isNewGame ) {
enable[NG_COL_REMOTE] = TRI_ENAB_ENABLED;
} else {
enable[NG_COL_REMOTE] = TRI_ENAB_DISABLED;
}
dValue.col = NG_COL_REMOTE;
(*ngc->getColProc)( ngc->closure, player, NG_COL_REMOTE,
deepCopy, &dValue );
isLocal = !dValue.value.ng_bool;
}
#endif
/* If remote is enabled and set, then if it's a new game all else is
hidden. But if it's not a new game, they're disabled. Password is
always hidden if robot is set. */
if ( isLocal ) {
XP_TriEnable tmp;
/* No changing name or robotness since they're sent to remote
host. */
tmp = (isNewGame || role == SERVER_STANDALONE)?
TRI_ENAB_ENABLED:TRI_ENAB_DISABLED;
enable[NG_COL_NAME] = tmp;
enable[NG_COL_ROBOT] = tmp;
/* Password and game info (the not isNewGame case): passwords are
not transmitted: they're local only. There's no harm in
allowing local players to change them. So passwords should be
enabled whenever it's not a robot regardless of both isNewGame
and role. */
dValue.col = NG_COL_ROBOT;
(*ngc->getColProc)( ngc->closure, player, NG_COL_ROBOT, deepCopy,
&dValue );
if ( !dValue.value.ng_bool ) {
/* If it's a robot, leave it hidden */
enable[NG_COL_PASSWD] = TRI_ENAB_ENABLED;
}
} else {
if ( isNewGame ) {
/* leave 'em hidden */
} else {
enable[NG_COL_NAME] = TRI_ENAB_DISABLED;
enable[NG_COL_ROBOT] = TRI_ENAB_DISABLED;
/* leave passwd hidden */
}
}
}
for ( col = 0; col < NG_NUM_COLS; ++col ) {
enableOne( ngc, player, col, enable[col], force );
}
} /* adjustOneRow */
/* changeRole. When role changes, number of players displayed, and which
* players, may change. Host shows all players (up to nPlayers). Guest shows
* only local players, but if role changes should show the rest. Change from
* Host or Standalone to guest should reduce the number shown.
*
* Here's the fun part: what happens when user changes nPlayers, then changes
* role? Say we're a guest with one player. User makes it two, than makes us
* host. Do we pull in a new player? No. Let's not change any of this stuff
* ONCE USER'S CHANGED NPLAYERS. Goal is to prevent his having to do that for
* the most common case, which is playing again with the same players. In
* that case changing role then back again should not lose/change data.
*/
#ifndef XWFEATURE_STANDALONE_ONLY
static XP_Bool
changeRole( NewGameCtx* ngc, DeviceRole newRole )
{
DeviceRole oldRole = ngc->role;
XP_Bool changing = oldRole != newRole;
if ( changing ) {
if ( !ngc->changedNPlayers ) {
NGValue value;
if ( newRole == SERVER_ISCLIENT ) {
value.ng_u16 = ngc->nLocalPlayers;
} else {
value.ng_u16 = ngc->nPlayersTotal;
}
if ( value.ng_u16 != ngc->nPlayersShown ) {
ngc->nPlayersShown = value.ng_u16;
(*ngc->setAttrProc)( ngc->closure, NG_ATTR_NPLAYERS, value );
}
}
ngc->role = newRole;
setRoleStrings( ngc );
}
return changing;
}
#endif
static void
setRoleStrings( NewGameCtx* ngc )
{
XP_U16 strID;
NGValue value;
void* closure = ngc->closure;
/* Tell client to set/change players label text, and also to add remote
checkbox column header if required. */
#ifndef XWFEATURE_STANDALONE_ONLY
(*ngc->enableAttrProc)( closure, NG_ATTR_REMHEADER,
( (ngc->role == SERVER_ISSERVER)
|| (!ngc->isNewGame
&& (ngc->role != SERVER_STANDALONE)) )?
TRI_ENAB_ENABLED : TRI_ENAB_HIDDEN );
#endif
if ( 0 ) {
#ifndef XWFEATURE_STANDALONE_ONLY
} else if ( ngc->role == SERVER_ISCLIENT && ngc->isNewGame ) {
strID = STR_LOCALPLAYERS;
#endif
} else {
strID = STR_TOTALPLAYERS;
}
value.ng_cp = util_getUserString( ngc->util, strID );
(*ngc->setAttrProc)( closure, NG_ATTR_NPLAYHEADER, value );
} /* setRoleStrings */
static void
considerEnable( NewGameCtx* ngc )
{
XP_TriEnable newEnable;
newEnable = (ngc->isNewGame && ngc->nPlayersShown > 1)?
TRI_ENAB_ENABLED : TRI_ENAB_HIDDEN;
if ( newEnable != ngc->juggleEnabled ) {
(*ngc->enableAttrProc)( ngc->closure, NG_ATTR_CANJUGGLE, newEnable );
ngc->juggleEnabled = newEnable;
}
#ifndef XWFEATURE_STANDALONE_ONLY
newEnable = (ngc->role == SERVER_STANDALONE)?
TRI_ENAB_HIDDEN : TRI_ENAB_ENABLED;
if ( newEnable != ngc->settingsEnabled ) {
ngc->settingsEnabled = newEnable;
(*ngc->enableAttrProc)( ngc->closure, NG_ATTR_CANCONFIG, newEnable );
}
#endif
} /* considerEnable */
static void
storePlayer( NewGameCtx* ngc, XP_U16 player, LocalPlayer* lp )
{
void* closure = ngc->closure;
NGCopyClosure cpcl;
cpcl.player = player;
cpcl.ngc = ngc;
cpcl.lp = lp;
for ( cpcl.col = 0; cpcl.col < NG_NUM_COLS; ++cpcl.col ) {
(*ngc->getColProc)( closure, cpcl.player, cpcl.col,
cpToLP, &cpcl );
}
}
static void
loadPlayer( NewGameCtx* ngc, XP_U16 player, const LocalPlayer* lp )
{
NGValue value;
void* closure = ngc->closure;
#ifndef XWFEATURE_STANDALONE_ONLY
value.ng_bool = !lp->isLocal;
(*ngc->setColProc)(closure, player, NG_COL_REMOTE, value );
#endif
value.ng_cp = lp->name;
(*ngc->setColProc)(closure, player, NG_COL_NAME, value );
value.ng_cp = lp->password;
(*ngc->setColProc)(closure, player, NG_COL_PASSWD, value );
value.ng_bool = LP_IS_ROBOT(lp);
(*ngc->setColProc)(closure, player, NG_COL_ROBOT, value );
}
#ifdef CPLUS
}
#endif