diff --git a/xwords4/common/model.c b/xwords4/common/model.c index cad333134..480adf488 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -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 diff --git a/xwords4/common/model.h b/xwords4/common/model.h index 9f02f2c77..438a04a06 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -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 ); diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 6b997750c..32d124b25 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -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 );