mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-27 09:58:45 +01:00
toward better recovery from out-of-sync move and undo: include a hash
of the move stack with every turn (version permitting), and drop incoming moves created against a move stack different from ours.
This commit is contained in:
parent
0e07fab731
commit
c52db8a8f0
3 changed files with 141 additions and 112 deletions
|
@ -936,6 +936,12 @@ model_currentMoveToStream( ModelCtxt* model, XP_S16 turn,
|
|||
nColsNBits = NUMCOLS_NBITS_4;
|
||||
#endif
|
||||
|
||||
#ifdef STREAM_VERS_BIGBOARD
|
||||
if ( STREAM_VERS_BIGBOARD <= stream_getVersion( stream ) ) {
|
||||
stream_putU32( stream, stack_getHash(model->vol.stack) );
|
||||
}
|
||||
#endif
|
||||
|
||||
XP_ASSERT( turn >= 0 );
|
||||
player = &model->players[turn];
|
||||
numTiles = player->nPending;
|
||||
|
@ -963,10 +969,11 @@ model_currentMoveToStream( ModelCtxt* model, XP_S16 turn,
|
|||
* assert that it's in the tray, remove it from the tray, and place it on the
|
||||
* board.
|
||||
*/
|
||||
void
|
||||
XP_Bool
|
||||
model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum,
|
||||
XWStreamCtxt* stream )
|
||||
{
|
||||
XP_Bool success = XP_TRUE;
|
||||
XP_U16 numTiles, ii;
|
||||
Tile blank = dict_getBlankTile( model_getDictionary(model) );
|
||||
XP_U16 nColsNBits;
|
||||
|
@ -978,40 +985,50 @@ model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum,
|
|||
#endif
|
||||
|
||||
model_resetCurrentTurn( model, playerNum );
|
||||
|
||||
numTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS );
|
||||
|
||||
XP_LOGF( "%s: numTiles=%d", __func__, numTiles );
|
||||
|
||||
for ( ii = 0; ii < numTiles; ++ii ) {
|
||||
XP_S16 foundAt;
|
||||
Tile moveTile;
|
||||
Tile tileFace = (Tile)stream_getBits( stream, TILE_NBITS );
|
||||
XP_U16 col = (XP_U16)stream_getBits( stream, nColsNBits );
|
||||
XP_U16 row = (XP_U16)stream_getBits( stream, nColsNBits );
|
||||
XP_Bool isBlank = stream_getBits( stream, 1 );
|
||||
|
||||
/* This code gets called both for the server, which has all the
|
||||
tiles in its tray, and for a client, which has "EMPTY" tiles
|
||||
only. If it's the empty case, we stuff a real tile into the
|
||||
tray before falling through to the normal case */
|
||||
|
||||
if ( isBlank ) {
|
||||
moveTile = blank;
|
||||
} else {
|
||||
moveTile = tileFace;
|
||||
}
|
||||
|
||||
foundAt = model_trayContains( model, playerNum, moveTile );
|
||||
if ( foundAt == -1 ) {
|
||||
XP_ASSERT( EMPTY_TILE == model_getPlayerTile(model, playerNum, 0));
|
||||
|
||||
(void)model_removePlayerTile( model, playerNum, -1 );
|
||||
model_addPlayerTile( model, playerNum, -1, moveTile );
|
||||
}
|
||||
|
||||
model_moveTrayToBoard( model, playerNum, col, row, foundAt, tileFace );
|
||||
#ifdef STREAM_VERS_BIGBOARD
|
||||
if ( STREAM_VERS_BIGBOARD <= stream_getVersion( stream ) ) {
|
||||
XP_U32 hash = stream_getU32( stream );
|
||||
success = hash == stack_getHash( model->vol.stack );
|
||||
}
|
||||
#endif
|
||||
if ( success ) {
|
||||
numTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS );
|
||||
|
||||
XP_LOGF( "%s: numTiles=%d", __func__, numTiles );
|
||||
|
||||
for ( ii = 0; ii < numTiles; ++ii ) {
|
||||
XP_S16 foundAt;
|
||||
Tile moveTile;
|
||||
Tile tileFace = (Tile)stream_getBits( stream, TILE_NBITS );
|
||||
XP_U16 col = (XP_U16)stream_getBits( stream, nColsNBits );
|
||||
XP_U16 row = (XP_U16)stream_getBits( stream, nColsNBits );
|
||||
XP_Bool isBlank = stream_getBits( stream, 1 );
|
||||
|
||||
/* This code gets called both for the server, which has all the
|
||||
tiles in its tray, and for a client, which has "EMPTY" tiles
|
||||
only. If it's the empty case, we stuff a real tile into the
|
||||
tray before falling through to the normal case */
|
||||
|
||||
if ( isBlank ) {
|
||||
moveTile = blank;
|
||||
} else {
|
||||
moveTile = tileFace;
|
||||
}
|
||||
|
||||
foundAt = model_trayContains( model, playerNum, moveTile );
|
||||
if ( foundAt == -1 ) {
|
||||
XP_ASSERT( EMPTY_TILE == model_getPlayerTile(model, playerNum, 0));
|
||||
|
||||
(void)model_removePlayerTile( model, playerNum, -1 );
|
||||
model_addPlayerTile( model, playerNum, -1, moveTile );
|
||||
}
|
||||
|
||||
model_moveTrayToBoard( model, playerNum, col, row, foundAt, tileFace );
|
||||
}
|
||||
} else {
|
||||
XP_LOGF( "%s: dropping move on hash mismatch", __func__ );
|
||||
}
|
||||
return success;
|
||||
} /* model_makeTurnFromStream */
|
||||
|
||||
void
|
||||
|
|
|
@ -198,8 +198,8 @@ void model_trayToStream( ModelCtxt* model, XP_S16 turn,
|
|||
XWStreamCtxt* stream );
|
||||
void model_currentMoveToStream( ModelCtxt* model, XP_S16 turn,
|
||||
XWStreamCtxt* stream);
|
||||
void model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum,
|
||||
XWStreamCtxt* stream );
|
||||
XP_Bool model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum,
|
||||
XWStreamCtxt* stream );
|
||||
void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum,
|
||||
const MoveInfo* newMove );
|
||||
|
||||
|
|
|
@ -1914,34 +1914,42 @@ sendMoveTo( ServerCtxt* server, XP_U16 devIndex, XP_U16 turn,
|
|||
stream_destroy( stream );
|
||||
} /* sendMoveTo */
|
||||
|
||||
static void
|
||||
static XP_Bool
|
||||
readMoveInfo( ServerCtxt* server, XWStreamCtxt* stream,
|
||||
XP_U16* whoMovedP, XP_Bool* isTradeP,
|
||||
TrayTileSet* newTiles, TrayTileSet* tradedTiles,
|
||||
XP_Bool* legalP )
|
||||
{
|
||||
XP_Bool success;
|
||||
XP_U16 whoMoved = stream_getBits( stream, PLAYERNUM_NBITS );
|
||||
XP_Bool legalMove = XP_TRUE;
|
||||
XP_Bool isTrade;
|
||||
|
||||
traySetFromStream( stream, newTiles );
|
||||
isTrade = stream_getBits( stream, 1 );
|
||||
success = pool_containsTiles( server->pool, newTiles );
|
||||
if ( success ) {
|
||||
isTrade = stream_getBits( stream, 1 );
|
||||
|
||||
if ( isTrade ) {
|
||||
traySetFromStream( stream, tradedTiles );
|
||||
XP_LOGF( "%s: got trade of %d tiles", __func__, tradedTiles->nTiles );
|
||||
} else {
|
||||
legalMove = stream_getBits( stream, 1 );
|
||||
model_makeTurnFromStream( server->vol.model, whoMoved, stream );
|
||||
if ( isTrade ) {
|
||||
traySetFromStream( stream, tradedTiles );
|
||||
XP_LOGF( "%s: got trade of %d tiles", __func__,
|
||||
tradedTiles->nTiles );
|
||||
} else {
|
||||
legalMove = stream_getBits( stream, 1 );
|
||||
success = model_makeTurnFromStream( server->vol.model,
|
||||
whoMoved, stream );
|
||||
getPlayerTime( server, stream, whoMoved );
|
||||
}
|
||||
|
||||
getPlayerTime( server, stream, whoMoved );
|
||||
if ( success ) {
|
||||
pool_removeTiles( server->pool, newTiles );
|
||||
|
||||
*whoMovedP = whoMoved;
|
||||
*isTradeP = isTrade;
|
||||
*legalP = legalMove;
|
||||
}
|
||||
}
|
||||
|
||||
pool_removeTiles( server->pool, newTiles );
|
||||
|
||||
*whoMovedP = whoMoved;
|
||||
*isTradeP = isTrade;
|
||||
*legalP = legalMove;
|
||||
return success;
|
||||
} /* readMoveInfo */
|
||||
|
||||
static void
|
||||
|
@ -1997,6 +2005,7 @@ makeMoveReportIf( ServerCtxt* server, XWStreamCtxt** wordsStream )
|
|||
static XP_Bool
|
||||
reflectMoveAndInform( ServerCtxt* server, XWStreamCtxt* stream )
|
||||
{
|
||||
XP_Bool success = XP_TRUE;
|
||||
ModelCtxt* model = server->vol.model;
|
||||
XP_U16 whoMoved;
|
||||
XP_U16 nTilesMoved = 0; /* trade case */
|
||||
|
@ -2013,76 +2022,78 @@ reflectMoveAndInform( ServerCtxt* server, XWStreamCtxt* stream )
|
|||
|
||||
XP_ASSERT( gi->serverRole == SERVER_ISSERVER );
|
||||
|
||||
readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles,
|
||||
&tradedTiles, &isLegalMove ); /* modifies model */
|
||||
XP_ASSERT( isLegalMove ); /* client should always report as true */
|
||||
success = readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles,
|
||||
&tradedTiles, &isLegalMove ); /* modifies model */
|
||||
XP_ASSERT( !success || isLegalMove ); /* client should always report as true */
|
||||
isLegalMove = XP_TRUE;
|
||||
|
||||
if ( isTrade ) {
|
||||
if ( success ) {
|
||||
if ( isTrade ) {
|
||||
|
||||
sendMoveToClientsExcept( server, whoMoved, XP_TRUE, &newTiles,
|
||||
&tradedTiles, sourceClientIndex );
|
||||
sendMoveToClientsExcept( server, whoMoved, XP_TRUE, &newTiles,
|
||||
&tradedTiles, sourceClientIndex );
|
||||
|
||||
model_makeTileTrade( model, whoMoved,
|
||||
&tradedTiles, &newTiles );
|
||||
pool_replaceTiles( server->pool, &tradedTiles );
|
||||
model_makeTileTrade( model, whoMoved,
|
||||
&tradedTiles, &newTiles );
|
||||
pool_replaceTiles( server->pool, &tradedTiles );
|
||||
|
||||
server->vol.showPrevMove = XP_TRUE;
|
||||
mvStream = makeTradeReportIf( server, &tradedTiles );
|
||||
server->vol.showPrevMove = XP_TRUE;
|
||||
mvStream = makeTradeReportIf( server, &tradedTiles );
|
||||
|
||||
} else {
|
||||
nTilesMoved = model_getCurrentMoveCount( model, whoMoved );
|
||||
isLegalMove = (nTilesMoved == 0)
|
||||
|| checkMoveAllowed( server, whoMoved );
|
||||
|
||||
/* I don't think this will work if there are more than two devices in
|
||||
a palm game; need to change state and get out of here before
|
||||
returning to send additional messages. PENDING(ehouse) */
|
||||
sendMoveToClientsExcept( server, whoMoved, isLegalMove, &newTiles,
|
||||
(TrayTileSet*)NULL, sourceClientIndex );
|
||||
|
||||
server->vol.showPrevMove = XP_TRUE;
|
||||
mvStream = makeMoveReportIf( server, &wordsStream );
|
||||
|
||||
model_commitTurn( model, whoMoved, &newTiles );
|
||||
resetEngines( server );
|
||||
}
|
||||
|
||||
if ( isLegalMove ) {
|
||||
XP_U16 nTilesLeft = model_getNumTilesTotal( model, whoMoved );
|
||||
|
||||
if ( (gi->phoniesAction == PHONIES_DISALLOW) && (nTilesMoved > 0) ) {
|
||||
server->lastMoveSource = sourceClientIndex;
|
||||
SETSTATE( server, XWSTATE_MOVE_CONFIRM_MUSTSEND );
|
||||
doRequest = XP_TRUE;
|
||||
} else if ( nTilesLeft > 0 ) {
|
||||
nextTurn( server, PICK_NEXT );
|
||||
} else {
|
||||
SETSTATE(server, XWSTATE_NEEDSEND_ENDGAME );
|
||||
nTilesMoved = model_getCurrentMoveCount( model, whoMoved );
|
||||
isLegalMove = (nTilesMoved == 0)
|
||||
|| checkMoveAllowed( server, whoMoved );
|
||||
|
||||
/* I don't think this will work if there are more than two devices in
|
||||
a palm game; need to change state and get out of here before
|
||||
returning to send additional messages. PENDING(ehouse) */
|
||||
sendMoveToClientsExcept( server, whoMoved, isLegalMove, &newTiles,
|
||||
(TrayTileSet*)NULL, sourceClientIndex );
|
||||
|
||||
server->vol.showPrevMove = XP_TRUE;
|
||||
mvStream = makeMoveReportIf( server, &wordsStream );
|
||||
|
||||
model_commitTurn( model, whoMoved, &newTiles );
|
||||
resetEngines( server );
|
||||
}
|
||||
|
||||
if ( isLegalMove ) {
|
||||
XP_U16 nTilesLeft = model_getNumTilesTotal( model, whoMoved );
|
||||
|
||||
if ( (gi->phoniesAction == PHONIES_DISALLOW) && (nTilesMoved > 0) ) {
|
||||
server->lastMoveSource = sourceClientIndex;
|
||||
SETSTATE( server, XWSTATE_MOVE_CONFIRM_MUSTSEND );
|
||||
doRequest = XP_TRUE;
|
||||
} else if ( nTilesLeft > 0 ) {
|
||||
nextTurn( server, PICK_NEXT );
|
||||
} else {
|
||||
SETSTATE(server, XWSTATE_NEEDSEND_ENDGAME );
|
||||
doRequest = XP_TRUE;
|
||||
}
|
||||
|
||||
if ( !!mvStream ) {
|
||||
XP_ASSERT( !server->nv.prevMoveStream );
|
||||
server->nv.prevMoveStream = mvStream;
|
||||
XP_ASSERT( !server->nv.prevWordsStream );
|
||||
server->nv.prevWordsStream = wordsStream;
|
||||
}
|
||||
} else {
|
||||
/* The client from which the move came still needs to be told. But we
|
||||
can't send a message now since we're burried in a message handler.
|
||||
(Palm, at least, won't manage.) So set up state to tell that
|
||||
client again in a minute. */
|
||||
SETSTATE( server, XWSTATE_NEEDSEND_BADWORD_INFO );
|
||||
server->lastMoveSource = sourceClientIndex;
|
||||
doRequest = XP_TRUE;
|
||||
}
|
||||
|
||||
if ( !!mvStream ) {
|
||||
XP_ASSERT( !server->nv.prevMoveStream );
|
||||
server->nv.prevMoveStream = mvStream;
|
||||
XP_ASSERT( !server->nv.prevWordsStream );
|
||||
server->nv.prevWordsStream = wordsStream;
|
||||
if ( doRequest ) {
|
||||
util_requestTime( server->vol.util );
|
||||
}
|
||||
} else {
|
||||
/* The client from which the move came still needs to be told. But we
|
||||
can't send a message now since we're burried in a message handler.
|
||||
(Palm, at least, won't manage.) So set up state to tell that
|
||||
client again in a minute. */
|
||||
SETSTATE( server, XWSTATE_NEEDSEND_BADWORD_INFO );
|
||||
server->lastMoveSource = sourceClientIndex;
|
||||
doRequest = XP_TRUE;
|
||||
}
|
||||
|
||||
if ( doRequest ) {
|
||||
util_requestTime( server->vol.util );
|
||||
}
|
||||
|
||||
return XP_TRUE;
|
||||
LOG_RETURNF( "%d", success );
|
||||
return success;
|
||||
} /* reflectMoveAndInform */
|
||||
|
||||
static XP_Bool
|
||||
|
@ -2101,9 +2112,10 @@ reflectMove( ServerCtxt* server, XWStreamCtxt* stream )
|
|||
moveOk = XWSTATE_INTURN == server->nv.gameState;
|
||||
XP_ASSERT( moveOk ); /* message permanently lost if dropped here! */
|
||||
if ( moveOk ) {
|
||||
readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles,
|
||||
&tradedTiles, &isLegal ); /* modifies model */
|
||||
|
||||
moveOk = readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles,
|
||||
&tradedTiles, &isLegal ); /* modifies model */
|
||||
}
|
||||
if ( moveOk ) {
|
||||
if ( isTrade ) {
|
||||
model_makeTileTrade( model, whoMoved, &tradedTiles, &newTiles );
|
||||
pool_replaceTiles( server->pool, &tradedTiles );
|
||||
|
|
Loading…
Reference in a new issue