xwords/xwords4/common/movestak.c

452 lines
13 KiB
C
Raw Normal View History

/* -*- compile-command: "cd ../linux && make -j5 MEMDEBUG=TRUE"; -*- */
2003-11-01 06:35:29 +01:00
/*
* Copyright 2001-2015 by Eric House (xwords@eehouse.org). All rights
2006-08-09 06:52:43 +02:00
* reserved.
2003-11-01 06:35:29 +01:00
*
* 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 "modelp.h"
#include "mempool.h"
#include "xwstream.h"
#include "movestak.h"
#include "memstream.h"
#include "strutils.h"
#include "dbgutil.h"
2003-11-01 06:35:29 +01:00
/* HASH_STREAM: It should be possible to hash the move stack by simply hashing
the stream from the beginning to the top of the undo stack (excluding
what's in the redo area), avoiding iterating over it and doing a ton of
bitwise operations to read it into entries. But I don't currently seem to
have a XWStreamPos that corresponds to the undo-top and so I can't figure
out the length. Hashing that includes the redo part of the stack doesn't
work once there's been undo activity. (Not sure why...) */
2003-11-01 06:35:29 +01:00
#ifdef CPLUS
extern "C" {
#endif
struct StackCtxt {
VTableMgr* vtmgr;
XWStreamCtxt* data;
XWStreamPos top;
XWStreamPos cachedPos;
XP_U16 cacheNext;
XP_U16 nEntries;
XP_U16 bitsPerTile;
2003-11-01 06:35:29 +01:00
XP_U16 highWaterMark;
DIRTY_SLOT
2003-11-01 06:35:29 +01:00
MPSLOT
};
void
stack_init( StackCtxt* stack )
{
stack->nEntries = stack->highWaterMark = 0;
stack->top = START_OF_STREAM;
/* I see little point in freeing or shrinking stack->data. It'll get
shrunk to fit as soon as we serialize/deserialize anyway. */
} /* stack_init */
#ifdef STREAM_VERS_HASHSTREAM
XP_U32
stack_getHash( const StackCtxt* stack, XP_Bool correct )
{
XP_U32 hash = 0;
if ( !!stack->data ) {
hash = stream_getHash( stack->data, stack->top, correct );
}
2012-05-25 07:22:00 +02:00
return hash;
} /* stack_getHash */
#endif
void
stack_setBitsPerTile( StackCtxt* stack, XP_U16 bitsPerTile )
{
XP_ASSERT( !!stack );
2011-08-18 16:49:40 +02:00
XP_ASSERT( bitsPerTile == 5 || bitsPerTile == 6 );
stack->bitsPerTile = bitsPerTile;
}
2003-11-01 06:35:29 +01:00
StackCtxt*
stack_make( MPFORMAL VTableMgr* vtmgr )
{
StackCtxt* result = (StackCtxt*)XP_MALLOC( mpool, sizeof( *result ) );
if ( !!result ) {
XP_MEMSET( result, 0, sizeof(*result) );
MPASSIGN(result->mpool, mpool);
result->vtmgr = vtmgr;
}
return result;
} /* stack_make */
void
stack_destroy( StackCtxt* stack )
{
if ( !!stack->data ) {
stream_destroy( stack->data );
}
/* Ok to close with a dirty stack, e.g. if not saving a deleted game */
// ASSERT_NOT_DIRTY( stack );
2003-11-01 06:35:29 +01:00
XP_FREE( stack->mpool, stack );
} /* stack_destroy */
void
stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream )
{
XP_U16 nBytes = stream_getU16( stream );
if ( nBytes > 0 ) {
stack->highWaterMark = stream_getU16( stream );
stack->nEntries = stream_getU16( stream );
stack->top = stream_getU32( stream );
stack->data = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr );
2003-11-01 06:35:29 +01:00
stream_getFromStream( stack->data, stream, nBytes );
2003-11-01 06:35:29 +01:00
} else {
XP_ASSERT( stack->nEntries == 0 );
XP_ASSERT( stack->top == 0 );
}
CLEAR_DIRTY( stack );
2003-11-01 06:35:29 +01:00
} /* stack_makeFromStream */
void
stack_writeToStream( const StackCtxt* stack, XWStreamCtxt* stream )
2003-11-01 06:35:29 +01:00
{
XP_U16 nBytes;
XWStreamCtxt* data = stack->data;
XWStreamPos oldPos = START_OF_STREAM;
if ( !!data ) {
oldPos = stream_setPos( data, POS_READ, START_OF_STREAM );
nBytes = stream_getSize( data );
} else {
nBytes = 0;
}
2003-11-01 06:35:29 +01:00
stream_putU16( stream, nBytes );
if ( nBytes > 0 ) {
stream_putU16( stream, stack->highWaterMark );
stream_putU16( stream, stack->nEntries );
stream_putU32( stream, stack->top );
stream_getFromStream( stream, data, nBytes );
/* in case it'll be used further */
(void)stream_setPos( data, POS_READ, oldPos );
2003-11-01 06:35:29 +01:00
}
CLEAR_DIRTY( stack );
2003-11-01 06:35:29 +01:00
} /* stack_writeToStream */
StackCtxt*
stack_copy( const StackCtxt* stack )
{
StackCtxt* newStack = NULL;
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(stack->mpool)
stack->vtmgr );
stack_writeToStream( stack, stream );
newStack = stack_make( MPPARM(stack->mpool) stack->vtmgr );
stack_loadFromStream( newStack, stream );
stack_setBitsPerTile( newStack, stack->bitsPerTile );
stream_destroy( stream );
return newStack;
}
2003-11-01 06:35:29 +01:00
static void
pushEntryImpl( StackCtxt* stack, const StackEntry* entry )
2003-11-01 06:35:29 +01:00
{
XP_U16 ii, bitsPerTile;
2003-11-01 06:35:29 +01:00
XWStreamPos oldLoc;
XP_U16 nTiles = entry->u.move.moveInfo.nTiles;
XWStreamCtxt* stream = stack->data;
if ( !stream ) {
stream = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr );
2003-11-01 06:35:29 +01:00
stack->data = stream;
}
oldLoc = stream_setPos( stream, POS_WRITE, stack->top );
2003-11-01 06:35:29 +01:00
stream_putBits( stream, 2, entry->moveType );
stream_putBits( stream, 2, entry->playerNum );
switch( entry->moveType ) {
case MOVE_TYPE:
case PHONY_TYPE:
stream_putBits( stream, NTILES_NBITS, nTiles );
stream_putBits( stream, 5, entry->u.move.moveInfo.commonCoord );
stream_putBits( stream, 1, entry->u.move.moveInfo.isHorizontal );
bitsPerTile = stack->bitsPerTile;
XP_ASSERT( bitsPerTile == 5 || bitsPerTile == 6 );
for ( ii = 0; ii < nTiles; ++ii ) {
Tile tile;
2003-11-01 06:35:29 +01:00
stream_putBits( stream, 5,
entry->u.move.moveInfo.tiles[ii].varCoord );
tile = entry->u.move.moveInfo.tiles[ii].tile;
stream_putBits( stream, bitsPerTile, tile & TILE_VALUE_MASK );
stream_putBits( stream, 1, (tile & TILE_BLANK_BIT) != 0 );
2003-11-01 06:35:29 +01:00
}
if ( entry->moveType == MOVE_TYPE ) {
traySetToStream( stream, &entry->u.move.newTiles );
}
break;
case ASSIGN_TYPE:
traySetToStream( stream, &entry->u.assign.tiles );
break;
case TRADE_TYPE:
XP_ASSERT( entry->u.trade.newTiles.nTiles
== entry->u.trade.oldTiles.nTiles );
traySetToStream( stream, &entry->u.trade.oldTiles );
/* could save three bits per trade by just writing the tiles of the
second guy */
traySetToStream( stream, &entry->u.trade.newTiles );
break;
}
++stack->nEntries;
stack->highWaterMark = stack->nEntries;
stack->top = stream_setPos( stream, POS_WRITE, oldLoc );
#ifdef DEBUG_HASHING
XP_LOGSTREAM( stack->data );
#endif
SET_DIRTY( stack );
} /* pushEntryImpl */
static void
pushEntry( StackCtxt* stack, const StackEntry* entry )
{
#ifdef DEBUG_HASHING
2016-01-02 04:48:22 +01:00
XP_Bool correct = XP_TRUE;
XP_U32 origHash = stack_getHash( stack, correct );
#endif
pushEntryImpl( stack, entry );
#ifdef DEBUG_HASHING
2016-01-02 04:48:22 +01:00
XP_U32 newHash = stack_getHash( stack, XP_TRUE );
StackEntry lastEntry;
if ( stack_popEntry( stack, &lastEntry ) ) {
2016-01-02 04:48:22 +01:00
XP_ASSERT( origHash == stack_getHash( stack, correct ) );
pushEntryImpl( stack, &lastEntry );
2016-01-02 04:48:22 +01:00
XP_ASSERT( newHash == stack_getHash( stack, correct ) );
XP_LOGF( "%s: all ok", __func__ );
}
#endif
}
2003-11-01 06:35:29 +01:00
static void
readEntry( const StackCtxt* stack, StackEntry* entry )
2003-11-01 06:35:29 +01:00
{
XP_U16 nTiles, ii, bitsPerTile;
2003-11-01 06:35:29 +01:00
XWStreamCtxt* stream = stack->data;
entry->moveType = (StackMoveType)stream_getBits( stream, 2 );
entry->playerNum = (XP_U8)stream_getBits( stream, 2 );
switch( entry->moveType ) {
case MOVE_TYPE:
case PHONY_TYPE:
nTiles = entry->u.move.moveInfo.nTiles =
(XP_U8)stream_getBits( stream, NTILES_NBITS );
XP_ASSERT( nTiles <= MAX_TRAY_TILES );
entry->u.move.moveInfo.commonCoord = (XP_U8)stream_getBits(stream, 5);
entry->u.move.moveInfo.isHorizontal = (XP_U8)stream_getBits(stream, 1);
bitsPerTile = stack->bitsPerTile;
XP_ASSERT( bitsPerTile == 5 || bitsPerTile == 6 );
for ( ii = 0; ii < nTiles; ++ii ) {
Tile tile;
entry->u.move.moveInfo.tiles[ii].varCoord =
2003-11-01 06:35:29 +01:00
(XP_U8)stream_getBits(stream, 5);
tile = (Tile)stream_getBits( stream, bitsPerTile );
if ( 0 != stream_getBits( stream, 1 ) ) {
tile |= TILE_BLANK_BIT;
}
entry->u.move.moveInfo.tiles[ii].tile = tile;
2003-11-01 06:35:29 +01:00
}
if ( entry->moveType == MOVE_TYPE ) {
traySetFromStream( stream, &entry->u.move.newTiles );
}
break;
case ASSIGN_TYPE:
traySetFromStream( stream, &entry->u.assign.tiles );
break;
case TRADE_TYPE:
traySetFromStream( stream, &entry->u.trade.oldTiles );
traySetFromStream( stream, &entry->u.trade.newTiles );
XP_ASSERT( entry->u.trade.newTiles.nTiles
== entry->u.trade.oldTiles.nTiles );
break;
}
} /* readEntry */
void
stack_addMove( StackCtxt* stack, XP_U16 turn, const MoveInfo* moveInfo,
const TrayTileSet* newTiles )
2003-11-01 06:35:29 +01:00
{
2018-03-01 04:23:36 +01:00
StackEntry move = {.playerNum = (XP_U8)turn,
.moveType = MOVE_TYPE,
};
2003-11-01 06:35:29 +01:00
XP_MEMCPY( &move.u.move.moveInfo, moveInfo, sizeof(move.u.move.moveInfo));
move.u.move.newTiles = *newTiles;
pushEntry( stack, &move );
} /* stack_addMove */
void
stack_addPhony( StackCtxt* stack, XP_U16 turn, const MoveInfo* moveInfo )
2003-11-01 06:35:29 +01:00
{
2018-03-01 04:23:36 +01:00
StackEntry move = {.playerNum = (XP_U8)turn,
.moveType = PHONY_TYPE,
};
2003-11-01 06:35:29 +01:00
XP_MEMCPY( &move.u.phony.moveInfo, moveInfo,
2008-05-31 05:26:16 +02:00
sizeof(move.u.phony.moveInfo));
2003-11-01 06:35:29 +01:00
pushEntry( stack, &move );
} /* stack_addPhony */
void
stack_addTrade( StackCtxt* stack, XP_U16 turn,
const TrayTileSet* oldTiles, const TrayTileSet* newTiles )
2003-11-01 06:35:29 +01:00
{
2018-03-01 04:23:36 +01:00
StackEntry move = { .playerNum = (XP_U8)turn,
.moveType = TRADE_TYPE,
};
2003-11-01 06:35:29 +01:00
move.u.trade.oldTiles = *oldTiles;
move.u.trade.newTiles = *newTiles;
pushEntry( stack, &move );
} /* stack_addTrade */
void
stack_addAssign( StackCtxt* stack, XP_U16 turn, const TrayTileSet* tiles )
2003-11-01 06:35:29 +01:00
{
2018-03-01 04:23:36 +01:00
StackEntry move = { .playerNum = (XP_U8)turn,
.moveType = ASSIGN_TYPE,
};
2003-11-01 06:35:29 +01:00
move.u.assign.tiles = *tiles;
pushEntry( stack, &move );
} /* stack_addAssign */
static XP_Bool
setCacheReadyFor( StackCtxt* stack, XP_U16 nn )
2003-11-01 06:35:29 +01:00
{
XP_U16 ii;
2003-11-01 06:35:29 +01:00
stream_setPos( stack->data, POS_READ, START_OF_STREAM );
for ( ii = 0; ii < nn; ++ii ) {
StackEntry dummy;
2003-11-01 06:35:29 +01:00
readEntry( stack, &dummy );
}
stack->cacheNext = nn;
stack->cachedPos = stream_getPos( stack->data, POS_READ );
2003-11-01 06:35:29 +01:00
return XP_TRUE;
} /* setCacheReadyFor */
XP_U16
stack_getNEntries( const StackCtxt* stack )
2003-11-01 06:35:29 +01:00
{
return stack->nEntries;
} /* stack_getNEntries */
XP_Bool
stack_getNthEntry( StackCtxt* stack, XP_U16 nn, StackEntry* entry )
2003-11-01 06:35:29 +01:00
{
XP_Bool found;
if ( nn >= stack->nEntries ) {
2003-11-01 06:35:29 +01:00
found = XP_FALSE;
} else if ( stack->cacheNext != nn ) {
2003-11-01 06:35:29 +01:00
XP_ASSERT( !!stack->data );
found = setCacheReadyFor( stack, nn );
XP_ASSERT( stack->cacheNext == nn );
2003-11-01 06:35:29 +01:00
} else {
found = XP_TRUE;
}
if ( found ) {
XWStreamPos oldPos = stream_setPos( stack->data, POS_READ,
stack->cachedPos );
2003-11-01 06:35:29 +01:00
readEntry( stack, entry );
entry->moveNum = (XP_U8)nn;
2003-11-01 06:35:29 +01:00
stack->cachedPos = stream_setPos( stack->data, POS_READ, oldPos );
2003-11-01 06:35:29 +01:00
++stack->cacheNext;
}
return found;
} /* stack_getNthEntry */
XP_Bool
stack_popEntry( StackCtxt* stack, StackEntry* entry )
{
XP_U16 nn = stack->nEntries - 1;
XP_Bool found = stack_getNthEntry( stack, nn, entry );
2003-11-01 06:35:29 +01:00
if ( found ) {
stack->nEntries = nn;
2003-11-01 06:35:29 +01:00
setCacheReadyFor( stack, nn ); /* set cachedPos by side-effect */
2003-11-01 06:35:29 +01:00
stack->top = stack->cachedPos;
}
#ifdef DEBUG_HASHING
XP_LOGSTREAM( stack->data );
#endif
2003-11-01 06:35:29 +01:00
return found;
} /* stack_popEntry */
XP_Bool
stack_redo( StackCtxt* stack, StackEntry* entry )
2003-11-01 06:35:29 +01:00
{
XP_Bool canRedo = (stack->nEntries + 1) <= stack->highWaterMark;
if ( canRedo ) {
2003-11-01 06:35:29 +01:00
++stack->nEntries;
if ( NULL != entry ) {
stack_getNthEntry( stack, stack->nEntries-1, entry );
}
2003-11-01 06:35:29 +01:00
setCacheReadyFor( stack, stack->nEntries );
stack->top = stack->cachedPos;
}
return canRedo;
2003-11-01 06:35:29 +01:00
} /* stack_redo */
#ifdef CPLUS
}
#endif