fix problems with clients in networked games making a move after the

game should end: check number of passes and that all players still
have tiles before running robot.
This commit is contained in:
ehouse 2005-03-15 15:18:58 +00:00
parent 2e0ee693bc
commit dd8f2f92c5

View file

@ -42,8 +42,6 @@ extern "C" {
#define sEND 0x73454e44 #define sEND 0x73454e44
#define MAX_PASSES 2 /* how many times can all players pass? */
#define LOCAL_ADDR NULL #define LOCAL_ADDR NULL
#define IS_ROBOT(p) ((p)->isRobot) #define IS_ROBOT(p) ((p)->isRobot)
@ -84,11 +82,10 @@ typedef struct ServerVolatiles {
someone quits before I can show the someone quits before I can show the
scores? PENDING(ehouse) */ scores? PENDING(ehouse) */
XP_Bool showPrevMove; XP_Bool showPrevMove;
XP_Bool connected; /* already pinged the relay? */
} ServerVolatiles; } ServerVolatiles;
typedef struct ServerNonvolatiles { typedef struct ServerNonvolatiles {
XP_U16 nPassesInRow;
XP_U8 nDevices; XP_U8 nDevices;
XW_State gameState; XW_State gameState;
XP_S8 currentTurn; /* invalid when game is over */ XP_S8 currentTurn; /* invalid when game is over */
@ -96,8 +93,6 @@ typedef struct ServerNonvolatiles {
XP_Bool showRobotScores; XP_Bool showRobotScores;
RemoteAddress addresses[MAX_NUM_PLAYERS]; RemoteAddress addresses[MAX_NUM_PLAYERS];
/* TrayTileSet tradedTiles; */
} ServerNonvolatiles; } ServerNonvolatiles;
struct ServerCtxt { struct ServerCtxt {
@ -116,6 +111,9 @@ struct ServerCtxt {
MPSLOT MPSLOT
}; };
#define NPASSES_OK(s) model_recentPassCountOk((s)->vol.model)
/******************************* prototypes *******************************/ /******************************* prototypes *******************************/
static void assignTilesToAll( ServerCtxt* server ); static void assignTilesToAll( ServerCtxt* server );
static void resetEngines( ServerCtxt* server ); static void resetEngines( ServerCtxt* server );
@ -135,7 +133,7 @@ static void sendBadWordMsgs( ServerCtxt* server );
static XP_Bool handleIllegalWord( ServerCtxt* server, static XP_Bool handleIllegalWord( ServerCtxt* server,
XWStreamCtxt* incomming ); XWStreamCtxt* incomming );
static void tellMoveWasLegal( ServerCtxt* server ); static void tellMoveWasLegal( ServerCtxt* server );
static XP_Bool tileCountsOk( ServerCtxt* server );
#endif #endif
#define PICK_NEXT -1 #define PICK_NEXT -1
@ -208,7 +206,7 @@ initServer( ServerCtxt* server )
} }
server->nv.nDevices = 1; /* local device (0) is always there */ server->nv.nDevices = 1; /* local device (0) is always there */
server->vol.connected = XP_FALSE;
} /* initServer */ } /* initServer */
ServerCtxt* ServerCtxt*
@ -236,7 +234,9 @@ getNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers )
{ {
XP_U16 i; XP_U16 i;
nv->nPassesInRow = (XP_U16)stream_getBits( stream, 3 ); /* This should go away when stream format changes */
(void)stream_getBits( stream, 3 ); /* was npassesinrow */
/* number of players is upper limit on device count */ /* number of players is upper limit on device count */
nv->nDevices = (XP_U8)stream_getBits( stream, PLAYERNUM_NBITS ); nv->nDevices = (XP_U8)stream_getBits( stream, PLAYERNUM_NBITS );
@ -256,9 +256,7 @@ putNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers )
{ {
XP_U16 i; XP_U16 i;
/* Gross hack: with four players nPassesInRow can get bigger than 8. stream_putBits( stream, 3, 0 ); /* was nPassesInRow */
Rather than change the data format I'm just capping it. */
stream_putBits( stream, 3, nv->nPassesInRow & 0x0007 );
/* number of players is upper limit on device count */ /* number of players is upper limit on device count */
stream_putBits( stream, PLAYERNUM_NBITS, nv->nDevices ); stream_putBits( stream, PLAYERNUM_NBITS, nv->nDevices );
@ -652,7 +650,6 @@ makeRobotMove( ServerCtxt* server )
able to trade for tiles to move and blowing the undo stack. able to trade for tiles to move and blowing the undo stack.
This will stop them, and should have no effect if there are any This will stop them, and should have no effect if there are any
human players making real moves. */ human players making real moves. */
++server->nv.nPassesInRow;
if ( !!stream ) { if ( !!stream ) {
XP_UCHAR buf[64]; XP_UCHAR buf[64];
@ -665,14 +662,19 @@ makeRobotMove( ServerCtxt* server )
} }
} else { } else {
/* if canMove is false, this is a fake move, a pass */ /* if canMove is false, this is a fake move, a pass */
model_makeTurnFromMoveInfo( model, turn, &newMove );
if ( !!stream ) { if ( canMove || NPASSES_OK(server) ) {
(void)model_checkMoveLegal( model, turn, stream, NULL ); model_makeTurnFromMoveInfo( model, turn, &newMove );
XP_ASSERT( !server->vol.prevMoveStream );
server->vol.prevMoveStream = stream; if ( !!stream ) {
(void)model_checkMoveLegal( model, turn, stream, NULL );
XP_ASSERT( !server->vol.prevMoveStream );
server->vol.prevMoveStream = stream;
}
result = server_commitMove( server );
} else {
result = XP_FALSE;
} }
result = server_commitMove( server );
} }
} }
@ -690,7 +692,7 @@ static XP_Bool
robotMovePending( ServerCtxt* server ) robotMovePending( ServerCtxt* server )
{ {
XP_S16 turn = server->nv.currentTurn; XP_S16 turn = server->nv.currentTurn;
if ( turn >= 0 ) { if ( turn >= 0 && tileCountsOk(server) && NPASSES_OK(server) ) {
CurGameInfo* gi = server->vol.gi; CurGameInfo* gi = server->vol.gi;
LocalPlayer* player = &gi->players[turn]; LocalPlayer* player = &gi->players[turn];
return IS_ROBOT(player) && IS_LOCAL(player); return IS_ROBOT(player) && IS_LOCAL(player);
@ -754,10 +756,15 @@ showPrevScore( ServerCtxt* server )
static void static void
connectRelay( ServerCtxt* server ) connectRelay( ServerCtxt* server )
{ {
XWStreamCtxt* stream = util_makeStreamFromAddr( server->vol.util, /* THIS is a gross HACK. Relay-specific stuff belongs in comms.c as an
CHANNEL_NONE ); additional protocol layer. PENDING(ehouse) */
stream_putBytes( stream, &stream, 1 ); if ( !server->vol.connected ) {
stream_close( stream ); XWStreamCtxt* stream = util_makeStreamFromAddr( server->vol.util,
CHANNEL_NONE );
stream_putBytes( stream, &stream, 1 );
stream_close( stream );
server->vol.connected = XP_TRUE;
}
} }
#else #else
# define connectRelay(s) # define connectRelay(s)
@ -1539,27 +1546,25 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn )
previous turn was or how many tiles he had: it's a sure thing he previous turn was or how many tiles he had: it's a sure thing he
"has" enough to be allowed to take the turn just undone. */ "has" enough to be allowed to take the turn just undone. */
playerTilesLeft = MAX_TRAY_TILES; playerTilesLeft = MAX_TRAY_TILES;
/* prevent game from ending immediately if this was already too high.
The right fix is to decrement it by the number being undone while
still keeping it >= 0, but that's too much code for what's a very
obscure bug.*/
server->nv.nPassesInRow = 0;
} }
SETSTATE( server, XWSTATE_INTURN ); /* unless game over */ SETSTATE( server, XWSTATE_INTURN ); /* unless game over */
if ( playerTilesLeft > 0 if ( playerTilesLeft > 0 && tileCountsOk(server) && NPASSES_OK(server) ) {
&& (server->nv.nPassesInRow / nPlayers < MAX_PASSES) ) {
player = &server->players[nxtTurn]; player = &server->players[nxtTurn];
server->nv.currentTurn = (XP_U8)nxtTurn; server->nv.currentTurn = (XP_U8)nxtTurn;
} else { } else {
/* I discover that the game should end. If I'm the client, though, /* I discover that the game should end. If I'm the client,
should I wait for the server to deduce this and send out a message. though, should I wait for the server to deduce this and send
I think so. */ out a message? I think so. Yes, but be sure not to compute
another PASS move. Just don't do anything! */
if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) { if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) {
SETSTATE( server, XWSTATE_NEEDSEND_ENDGAME ); SETSTATE( server, XWSTATE_NEEDSEND_ENDGAME );
moreToDo = XP_TRUE; moreToDo = XP_TRUE;
} else {
XP_LOGF( "Doing nothing; waiting for server to end game." );
/* I'm the client. Do ++nothing++. */
} }
} }
@ -1909,12 +1914,6 @@ server_commitMove( ServerCtxt* server )
XP_ASSERT( turn >= 0 ); XP_ASSERT( turn >= 0 );
nTilesMoved = model_getCurrentMoveCount( model, turn ); nTilesMoved = model_getCurrentMoveCount( model, turn );
if ( nTilesMoved > 0 ) {
server->nv.nPassesInRow = 0;
} else {
++server->nv.nPassesInRow;
}
fetchTiles( server, turn, nTilesMoved, NULL, &newTiles ); fetchTiles( server, turn, nTilesMoved, NULL, &newTiles );
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
@ -2070,6 +2069,29 @@ server_endGame( ServerCtxt* server )
} /* server_endGame */ } /* server_endGame */
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
/* If game is about to end because one player's out of tiles, we don't want to
* keep trying to move */
static XP_Bool
tileCountsOk( ServerCtxt* server )
{
XP_Bool maybeOver = 0 == pool_getNTilesLeft( server->pool );
if ( maybeOver ) {
ModelCtxt* model = server->vol.model;
XP_U16 nPlayers = server->vol.gi->nPlayers;
XP_Bool zeroFound = XP_FALSE;
while ( nPlayers-- ) {
XP_U16 count = model_getNumTilesInTray( model, nPlayers );
if ( count == 0 ) {
zeroFound = XP_TRUE;
break;
}
}
maybeOver = zeroFound;
}
return !maybeOver;
} /* tileCountsOk */
static void static void
tellMoveWasLegal( ServerCtxt* server ) tellMoveWasLegal( ServerCtxt* server )
{ {