From 5776da0b9351e226b7f557211ade008697be4ea4 Mon Sep 17 00:00:00 2001 From: ehouse Date: Sat, 1 Nov 2003 05:35:29 +0000 Subject: [PATCH] first checkin --- xwords4/common/Makefile | 5 + xwords4/common/board.c | 2625 ++++++++++++++++++++++++++ xwords4/common/board.h | 163 ++ xwords4/common/boardp.h | 191 ++ xwords4/common/commmgr.h | 54 + xwords4/common/comms.c | 719 +++++++ xwords4/common/comms.h | 93 + xwords4/common/comtypes.h | 111 ++ xwords4/common/config.mk | 70 + xwords4/common/dawg.h | 75 + xwords4/common/dictnry.c | 482 +++++ xwords4/common/dictnry.h | 154 ++ xwords4/common/dictnryp.h | 36 + xwords4/common/draw.c | 370 ++++ xwords4/common/draw.h | 242 +++ xwords4/common/engine.c | 1105 +++++++++++ xwords4/common/engine.h | 61 + xwords4/common/game.c | 425 +++++ xwords4/common/game.h | 99 + xwords4/common/mempool.c | 244 +++ xwords4/common/mempool.h | 52 + xwords4/common/memstream.c | 416 ++++ xwords4/common/memstream.h | 46 + xwords4/common/model.c | 1488 +++++++++++++++ xwords4/common/model.h | 238 +++ xwords4/common/modelp.h | 78 + xwords4/common/movestak.c | 351 ++++ xwords4/common/movestak.h | 94 + xwords4/common/mscore.c | 828 ++++++++ xwords4/common/pool.c | 233 +++ xwords4/common/pool.h | 43 + xwords4/common/rules.mk | 31 + xwords4/common/server.c | 2270 ++++++++++++++++++++++ xwords4/common/server.h | 124 ++ xwords4/common/states.h | 61 + xwords4/common/strutils.c | 165 ++ xwords4/common/strutils.h | 52 + xwords4/common/tray.c | 659 +++++++ xwords4/common/util.h | 199 ++ xwords4/common/virtuals.h | 33 + xwords4/common/vtabmgr.c | 75 + xwords4/common/vtabmgr.h | 50 + xwords4/common/xwproto.h | 53 + xwords4/common/xwstate.h | 29 + xwords4/common/xwstream.h | 136 ++ xwords4/linux/LocalizedStrIncludes.h | 51 + xwords4/linux/Makefile | 120 ++ xwords4/linux/README.txt | 52 + xwords4/linux/cursesask.c | 189 ++ xwords4/linux/cursesask.h | 29 + xwords4/linux/cursesdraw.c | 500 +++++ xwords4/linux/cursesmain.c | 950 ++++++++++ xwords4/linux/cursesmain.h | 119 ++ xwords4/linux/filestream.c | 169 ++ xwords4/linux/filestream.h | 26 + xwords4/linux/flip.xpm | 14 + xwords4/linux/gtkask.c | 83 + xwords4/linux/gtkask.h | 32 + xwords4/linux/gtkdraw.c | 913 +++++++++ xwords4/linux/gtkdraw.h | 27 + xwords4/linux/gtkletterask.c | 108 ++ xwords4/linux/gtkletterask.h | 31 + xwords4/linux/gtkmain.c | 1537 +++++++++++++++ xwords4/linux/gtkmain.h | 121 ++ xwords4/linux/gtknewgame.c | 436 +++++ xwords4/linux/gtknewgame.h | 40 + xwords4/linux/gtkpasswdask.c | 94 + xwords4/linux/gtkpasswdask.h | 31 + xwords4/linux/hint.xpm | 17 + xwords4/linux/juggle.xpm | 14 + xwords4/linux/linuxdict.c | 372 ++++ xwords4/linux/linuxmain.c | 762 ++++++++ xwords4/linux/linuxmain.h | 50 + xwords4/linux/linuxserver.c | 34 + xwords4/linux/linuxserver.h | 31 + xwords4/linux/main.h | 81 + xwords4/linux/scripts/playnum.sh | 15 + xwords4/linux/scripts/wordlens.pl | 23 + xwords4/linux/value.xpm | 18 + xwords4/linux/xptypes.h | 113 ++ 80 files changed, 22330 insertions(+) create mode 100644 xwords4/common/Makefile create mode 100644 xwords4/common/board.c create mode 100644 xwords4/common/board.h create mode 100644 xwords4/common/boardp.h create mode 100644 xwords4/common/commmgr.h create mode 100644 xwords4/common/comms.c create mode 100644 xwords4/common/comms.h create mode 100644 xwords4/common/comtypes.h create mode 100644 xwords4/common/config.mk create mode 100644 xwords4/common/dawg.h create mode 100644 xwords4/common/dictnry.c create mode 100644 xwords4/common/dictnry.h create mode 100644 xwords4/common/dictnryp.h create mode 100644 xwords4/common/draw.c create mode 100644 xwords4/common/draw.h create mode 100644 xwords4/common/engine.c create mode 100644 xwords4/common/engine.h create mode 100644 xwords4/common/game.c create mode 100644 xwords4/common/game.h create mode 100644 xwords4/common/mempool.c create mode 100644 xwords4/common/mempool.h create mode 100644 xwords4/common/memstream.c create mode 100644 xwords4/common/memstream.h create mode 100644 xwords4/common/model.c create mode 100644 xwords4/common/model.h create mode 100644 xwords4/common/modelp.h create mode 100644 xwords4/common/movestak.c create mode 100644 xwords4/common/movestak.h create mode 100644 xwords4/common/mscore.c create mode 100644 xwords4/common/pool.c create mode 100644 xwords4/common/pool.h create mode 100644 xwords4/common/rules.mk create mode 100644 xwords4/common/server.c create mode 100644 xwords4/common/server.h create mode 100644 xwords4/common/states.h create mode 100644 xwords4/common/strutils.c create mode 100644 xwords4/common/strutils.h create mode 100644 xwords4/common/tray.c create mode 100644 xwords4/common/util.h create mode 100644 xwords4/common/virtuals.h create mode 100644 xwords4/common/vtabmgr.c create mode 100644 xwords4/common/vtabmgr.h create mode 100644 xwords4/common/xwproto.h create mode 100644 xwords4/common/xwstate.h create mode 100644 xwords4/common/xwstream.h create mode 100644 xwords4/linux/LocalizedStrIncludes.h create mode 100644 xwords4/linux/Makefile create mode 100644 xwords4/linux/README.txt create mode 100644 xwords4/linux/cursesask.c create mode 100644 xwords4/linux/cursesask.h create mode 100644 xwords4/linux/cursesdraw.c create mode 100644 xwords4/linux/cursesmain.c create mode 100644 xwords4/linux/cursesmain.h create mode 100644 xwords4/linux/filestream.c create mode 100644 xwords4/linux/filestream.h create mode 100644 xwords4/linux/flip.xpm create mode 100644 xwords4/linux/gtkask.c create mode 100644 xwords4/linux/gtkask.h create mode 100644 xwords4/linux/gtkdraw.c create mode 100644 xwords4/linux/gtkdraw.h create mode 100644 xwords4/linux/gtkletterask.c create mode 100644 xwords4/linux/gtkletterask.h create mode 100644 xwords4/linux/gtkmain.c create mode 100644 xwords4/linux/gtkmain.h create mode 100644 xwords4/linux/gtknewgame.c create mode 100644 xwords4/linux/gtknewgame.h create mode 100644 xwords4/linux/gtkpasswdask.c create mode 100644 xwords4/linux/gtkpasswdask.h create mode 100644 xwords4/linux/hint.xpm create mode 100644 xwords4/linux/juggle.xpm create mode 100644 xwords4/linux/linuxdict.c create mode 100644 xwords4/linux/linuxmain.c create mode 100644 xwords4/linux/linuxmain.h create mode 100644 xwords4/linux/linuxserver.c create mode 100644 xwords4/linux/linuxserver.h create mode 100644 xwords4/linux/main.h create mode 100755 xwords4/linux/scripts/playnum.sh create mode 100755 xwords4/linux/scripts/wordlens.pl create mode 100644 xwords4/linux/value.xpm create mode 100644 xwords4/linux/xptypes.h diff --git a/xwords4/common/Makefile b/xwords4/common/Makefile new file mode 100644 index 000000000..81d6dd87c --- /dev/null +++ b/xwords4/common/Makefile @@ -0,0 +1,5 @@ +# -*- mode: Makefile; -*- + +clean: + rm -rf $(PLATFORM)/*.o + diff --git a/xwords4/common/board.c b/xwords4/common/board.c new file mode 100644 index 000000000..635f1e9ad --- /dev/null +++ b/xwords4/common/board.c @@ -0,0 +1,2625 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (fixin@peak.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 "comtypes.h" +#include "board.h" +#include "game.h" +#include "server.h" +#include "comms.h" /* for CHANNEL_NONE */ +#include "dictnry.h" +#include "draw.h" +#include "engine.h" +#include "util.h" +#include "mempool.h" /* debug only */ +#include "memstream.h" +#include "strutils.h" +#include "LocalizedStrIncludes.h" + +#include "boardp.h" + +#define bEND 0x62454e44 + +#ifdef CPLUS +extern "C" { +#endif + +/****************************** prototypes ******************************/ +static XP_Bool getCellRect( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Rect* rect); +static void coordToCell( BoardCtxt* board, XP_U16 x, XP_U16 y, + XP_U16* colP, XP_U16* rowP ); +static XP_Bool drawCell( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool skipBlanks ); +static void figureBoardRect( BoardCtxt* board ); + +static void drawTimer( BoardCtxt* board ); +static void drawScoreBoard( BoardCtxt* board ); +static void invalCell( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool doMirror ); +static void +invalCellsUnderRect( BoardCtxt* board, XP_Rect* rect, XP_Bool doMirror ); + +static XP_Bool moveTileToBoard( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_U16 tileIndex, Tile blankFace ); +static XP_Bool rectsIntersect( XP_Rect* rect1, XP_Rect* rect2 ); +static XP_Bool rectContainsRect( XP_Rect* rect1, XP_Rect* rect2 ); +static void boardCellChanged( void* board, XP_U16 turn, XP_U16 col, + XP_U16 row, XP_Bool added ); +static void boardTileChanged( void* board, XP_U16 turn, TileBit bits ); +static void boardTurnChanged( void* board ); +static void boardGameOver( void* board ); +static void setArrow( BoardCtxt* board, XP_U16 row, XP_U16 col ); +static void setArrowFor( BoardCtxt* board, XP_U16 player, XP_U16 col, + XP_U16 row ); +static XP_S16 figureScorePlayerTapped( BoardCtxt* board, XP_U16 x, XP_U16 y ); +static XP_Bool cellOccupied( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool inclPending ); +static void makeMiniWindowForTrade( BoardCtxt* board ); +static void makeMiniWindowForText( BoardCtxt* board, XP_UCHAR* text, + MiniWindowType winType ); +static void invalTradeWindow( BoardCtxt* board, XP_S16 turn, XP_Bool redraw ); +static void invalSelTradeWindow( BoardCtxt* board ); +static void setTimerIf( BoardCtxt* board ); +static XP_Bool replaceLastTile( BoardCtxt* board ); +static XP_Bool setTrayVisState( BoardCtxt* board, XW_TrayVisState newState ); +static void advanceArrow( BoardCtxt* board ); + +#ifdef KEY_SUPPORT +static XP_Bool getArrow( BoardCtxt* board, XP_U16* col, XP_U16* row ); +static XP_Bool board_moveArrow( BoardCtxt* board, XP_Key cursorKey, + XP_Bool canCycle ); + +static XP_Bool setArrowVisible( BoardCtxt* board, XP_Bool visible ); +static XP_Bool setArrowVisibleFor( BoardCtxt* board, XP_U16 player, + XP_Bool visible ); +static XP_Bool moveKeyTileToBoard( BoardCtxt* board, XP_Key cursorKey ); +#endif + +#ifdef KEYBOARD_NAV +static XP_Bool moveScoreCursor( BoardCtxt* board, XP_Key key ); +static XP_Bool board_moveCursor( BoardCtxt* board, XP_Key cursorKey ); +#endif + +/***************************************************************************** + * + ****************************************************************************/ +BoardCtxt* +board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, DrawCtx* draw, + XW_UtilCtxt* util ) +{ + BoardCtxt* result = (BoardCtxt*)XP_MALLOC( mpool, sizeof( *result ) ); + + if ( result != NULL ) { + + XP_MEMSET( result, 0, sizeof( *result ) ); + + MPASSIGN(result->mpool, mpool); + + result->model = model; + result->server = server; + + result->draw = draw; + result->util = util; + result->gi = util->gameInfo; + XP_ASSERT( !!result->gi ); + + result->trayVisState = TRAY_HIDDEN; + + result->star_row = model_numRows(model) / 2; + + /* could just pass in invalCell.... PENDING(eeh) */ + model_setBoardListener( model, boardCellChanged, result ); + model_setTrayListener( model, boardTileChanged, result ); + server_setTurnChangeListener( server, boardTurnChanged, result ); + server_setGameOverListener( server, boardGameOver, result ); + + setTimerIf( result ); + } + return result; +} /* board_make */ + +void +board_destroy( BoardCtxt* board ) +{ + XP_FREE( board->mpool, board ); +} /* board_destroy */ + +BoardCtxt* +board_makeFromStream( MPFORMAL XWStreamCtxt* stream, ModelCtxt* model, + ServerCtxt* server, DrawCtx* draw, XW_UtilCtxt* util, + XP_U16 nPlayers ) +{ + BoardCtxt* board; + XP_U16 i; + + board = board_make( MPPARM(mpool) model, server, draw, util ); + + board->yOffset = (XP_U16)stream_getBits( stream, 2 ); + board->isFlipped = (XP_Bool)stream_getBits( stream, 1 ); + board->gameOver = (XP_Bool)stream_getBits( stream, 1 ); + board->showColors = (XP_Bool)stream_getBits( stream, 1 ); + board->showCellValues = (XP_Bool)stream_getBits( stream, 1 ); + + XP_ASSERT( !!server ); + + for ( i = 0; i < nPlayers; ++i ) { + BoardArrow* arrow = &board->boardArrow[i]; + arrow->col = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + arrow->row = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + arrow->vert = (XP_Bool)stream_getBits( stream, 1 ); + arrow->visible = (XP_Bool)stream_getBits( stream, 1 ); + + board->dividerLoc[i] = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + board->traySelBits[i] = (TileBit)stream_getBits( stream, MAX_TRAY_TILES ); + board->tradeInProgress[i] = (XP_Bool)stream_getBits( stream, 1 ); + } + + board->selPlayer = (XP_U8)stream_getBits( stream, PLAYERNUM_NBITS ); + board->trayVisState = (XW_TrayVisState)stream_getBits( stream, 2 ); + + XP_ASSERT( stream_getU32( stream ) == bEND ); + return board; +} /* board_makeFromStream */ + +void +board_writeToStream( BoardCtxt* board, XWStreamCtxt* stream ) +{ + XP_U16 nPlayers, i; + + stream_putBits( stream, 2, board->yOffset ); + stream_putBits( stream, 1, board->isFlipped ); + stream_putBits( stream, 1, board->gameOver ); + stream_putBits( stream, 1, board->showColors ); + stream_putBits( stream, 1, board->showCellValues ); + + XP_ASSERT( !!board->server ); + nPlayers = board->gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + BoardArrow* arrow = &board->boardArrow[i]; + stream_putBits( stream, NUMCOLS_NBITS, arrow->col ); + stream_putBits( stream, NUMCOLS_NBITS, arrow->row ); + stream_putBits( stream, 1, arrow->vert ); + stream_putBits( stream, 1, arrow->visible ); + + stream_putBits( stream, NTILES_NBITS, board->dividerLoc[i] ); + stream_putBits( stream, MAX_TRAY_TILES, board->traySelBits[i] ); + stream_putBits( stream, 1, board->tradeInProgress[i] ); + } + + stream_putBits( stream, PLAYERNUM_NBITS, board->selPlayer ); + stream_putBits( stream, 2, board->trayVisState ); + +#ifdef DEBUG + stream_putU32( stream, bEND ); +#endif +} /* board_writeToStream */ + +void +board_reset( BoardCtxt* board ) +{ + XP_U16 i; + XW_TrayVisState newState; + + XP_ASSERT( !!board->model ); + + /* This is appropriate for a new game *ONLY*. reset */ + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + board->traySelBits[i] = 0; + board->tradeInProgress[i] = XP_FALSE; + } + XP_MEMSET( &board->boardArrow, 0, sizeof(board->boardArrow) ); + board->gameOver = XP_FALSE; + board->selPlayer = 0; + board->star_row = model_numRows(board->model) / 2; + + newState = board->boardObscuresTray? TRAY_HIDDEN:TRAY_REVERSED; + setTrayVisState( board, newState ); + + board_invalAll( board ); + + setTimerIf( board ); +} /* board_reset */ + +void +board_setPos( BoardCtxt* board, XP_U16 left, XP_U16 top, + XP_Bool leftHanded ) +{ + board->boardBounds.left = left; + board->boardBounds.top = top; + board->leftHanded = leftHanded; + + figureBoardRect( board ); +} /* board_setPos */ + +void +board_setScoreboardLoc( BoardCtxt* board, XP_U16 scoreLeft, XP_U16 scoreTop, + XP_U16 scoreWidth, XP_U16 scoreHeight, + XP_Bool divideHorizontally ) +{ + board->scoreBdBounds.left = scoreLeft; + board->scoreBdBounds.top = scoreTop; + board->scoreBdBounds.width = scoreWidth; + board->scoreBdBounds.height = scoreHeight; + board->scoreSplitHor = divideHorizontally; +} /* board_setScoreboardLoc */ + +void +board_setTimerLoc( BoardCtxt* board, + XP_U16 timerLeft, XP_U16 timerTop, + XP_U16 timerWidth, XP_U16 timerHeight ) +{ + board->timerBounds.left = timerLeft; + board->timerBounds.top = timerTop; + board->timerBounds.width = timerWidth; + board->timerBounds.height = timerHeight; +} /* board_setTimerLoc */ + +void +board_setScale( BoardCtxt* board, XP_U16 hScale, XP_U16 vScale ) +{ + if ( hScale != board->boardHScale || vScale != board->boardVScale ) { + board->boardVScale = vScale; + board->boardHScale = hScale; + figureBoardRect( board ); + board_invalAll( board ); + } +} /* board_setScale */ + +void +board_getScale( BoardCtxt* board, XP_U16* hScale, XP_U16* vScale ) +{ + *hScale = board->boardHScale; + *vScale = board->boardVScale; +} /* board_getScale */ + +XP_Bool +board_prefsChanged( BoardCtxt* board, CommonPrefs* cp ) +{ + XP_Bool changed; + XP_Bool oldVal = board->disableArrow; + + board->disableArrow = !cp->showBoardArrow; + changed = oldVal != board->disableArrow; + + if ( changed ) { + changed = setArrowVisible( board, XP_FALSE ); + } + + return changed; +} /* board_prefsChanged */ + +XP_Bool +board_setYOffset( BoardCtxt* board, XP_U16 offset, XP_Bool invalRevealed ) +{ + XP_U16 oldOffset = board->yOffset; + XP_Bool result = oldOffset != offset; + + if ( result ) { + /* check if scrolling makes sense for this board in its current + state. */ + XP_U16 visibleHeight = board->boardBounds.height; + XP_U16 fullHeight = model_numRows(board->model) * board->boardVScale; + result = visibleHeight < fullHeight; + + if ( result ) { + invalSelTradeWindow( board ); + board->yOffset = offset; + figureBoardRect( board ); + util_yOffsetChange( board->util, oldOffset, offset ); + invalSelTradeWindow( board ); + board->needsDrawing = XP_TRUE; + } + } + + return result; +} /* board_setYOffset */ + +XP_U16 +board_getYOffset( BoardCtxt* board ) +{ + return board->yOffset; +} /* board_getYOffset */ + +#ifdef KEYBOARD_NAV +/* called by ncurses version only */ +BoardObjectType +board_getFocusOwner( BoardCtxt* board ) +{ + return board->focussed; +} /* board_getFocusOwner */ +#endif + +static void +invalArrowCell( BoardCtxt* board, XP_Bool doMirror ) +{ + BoardArrow* arrow = &board->boardArrow[board->selPlayer]; + invalCell( board, arrow->col, arrow->row, doMirror ); +} /* invalArrowCell */ + +#ifdef KEYBOARD_NAV +static void +invalCursorCell( BoardCtxt* board, XP_Bool doMirror ) +{ + BdCursorLoc loc = board->bdCursor[board->selPlayer]; + invalCell( board, loc.col, loc.row, doMirror ); +} /* invalCursorCell */ +#endif + +static void +invalTradeWindow( BoardCtxt* board, XP_S16 turn, XP_Bool redraw ) +{ + XP_ASSERT( turn >= 0 ); + + if ( board->tradeInProgress[turn] ) { + MiniWindowStuff* stuff = &board->miniWindowStuff[MINIWINDOW_TRADING]; + invalCellsUnderRect( board, &stuff->rect, XP_FALSE ); + if ( redraw ) { + board->tradingMiniWindowInvalid = XP_TRUE; + } + } +} /* invalTradeWindow */ + +static void +invalSelTradeWindow( BoardCtxt* board ) +{ + invalTradeWindow( board, board->selPlayer, + board->trayVisState == TRAY_REVEALED ); +} /* invalSelTradeWindow */ + +#ifdef POINTER_SUPPORT +static void +hideMiniWindow( BoardCtxt* board, XP_Bool destroy, MiniWindowType winType ) +{ + XP_Bool invalCovers; + MiniWindowStuff* stuff = &board->miniWindowStuff[winType]; + + draw_eraseMiniWindow( board->draw, &stuff->rect, + destroy, &stuff->closure, &invalCovers ); + if ( invalCovers ) { + invalCellsUnderRect( board, &stuff->rect, XP_FALSE ); + } + + if ( destroy ) { + stuff->text = (XP_UCHAR*)NULL; + } +} /* hideMiniWindow */ +#endif + +static XP_Bool +warnBadWords( XP_UCHAR* word, void* closure ) +{ + BadWordInfo bwi; + XP_Bool ok; + BoardCtxt* board = (BoardCtxt*)closure; + XP_S16 turn = server_getCurrentTurn( board->server ); + + bwi.nWords = 1; + bwi.words[0] = word; + + ok = util_warnIllegalWord( board->util, &bwi, turn, XP_FALSE ); + board->badWordRejected = !ok || board->badWordRejected; + + return ok; +} /* warnBadWords */ + +XP_Bool +board_commitTurn( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + XP_S16 turn = server_getCurrentTurn( board->server ); + + if ( board->gameOver || turn < 0 ) { + /* do nothing */ + } else if ( turn != board->selPlayer ) { + util_userError( board->util, ERR_NOT_YOUR_TURN ); + } else if ( checkRevealTray( board ) ) { + if ( board->tradeInProgress[turn] ) { + result = XP_TRUE; /* there's at least the window to clean up + after */ + + invalSelTradeWindow( board ); + board->tradeInProgress[turn] = XP_FALSE; + + if ( util_userQuery( board->util, QUERY_COMMIT_TRADE, + (XWStreamCtxt*)NULL ) ) { + result = server_commitTrade( board->server, + board->traySelBits[turn] ); + /* XP_DEBUGF( "server_commitTrade returned %d\n", result ); */ + board->traySelBits[turn] = 0x00; + } + } else { + XP_Bool warn, legal; + WordNotifierInfo info; + XWStreamCtxt* stream = + mem_stream_make( MPPARM(board->mpool) + util_getVTManager(board->util), NULL, + CHANNEL_NONE, (MemStreamCloseCallback)NULL ); + + XP_UCHAR* str = util_getUserString(board->util, STR_COMMIT_CONFIRM); + + stream_putBytes( stream, (void*)str, + (XP_U16)XP_STRLEN((const char*)str) ); + + warn = board->util->gameInfo->phoniesAction == PHONIES_WARN; + + board->badWordRejected = XP_FALSE; + info.proc = warnBadWords; + info.closure = board; + legal = model_checkMoveLegal( board->model, turn, stream, + warn? &info:(WordNotifierInfo*)NULL); + + if ( !legal || board->badWordRejected ) { + result = XP_FALSE; + } else { + /* hide the tray so no peeking */ + result = board_hideTray( board ); + if ( util_userQuery( board->util, QUERY_COMMIT_TURN, + stream ) ) { + result = server_commitMove( board->server ) || result; + /* invalidate any selected tiles in case we'll be drawing + this tray again rather than some other -- as is the + case in a two-player game where one's a robot. */ + board_invalTrayTiles( board, board->traySelBits[turn] ); + board->traySelBits[turn] = 0x00; + } + } + stream_destroy( stream ); + + if ( result ) { + setArrowVisibleFor( board, turn, XP_FALSE ); + } + } + } + return result; +} /* board_commitTurn */ + +/* Make all the changes necessary for the board to reflect the currently + * selected player. Many slots are duplicated on a per-player basis, e.g. + * cursor and tray traySelBits. Others, such as the miniwindow stuff, are + * singletons that may have to be hidden or shown. + */ +static void +board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer ) +{ + if ( !board->gameOver && server_getCurrentTurn(board->server) < 0 ) { + /* game not started yet; do nothing */ + } else if ( board->selPlayer == newPlayer ) { + checkRevealTray( board ); + } else { + XP_U16 oldPlayer = board->selPlayer; + model_foreachPendingCell( board->model, newPlayer, + boardCellChanged, board ); + model_foreachPendingCell( board->model, oldPlayer, + boardCellChanged, board ); + + /* if there are pending cells on one view and not the other, then the + previous move will be drawn highlighted on one and not the other + and so needs to be invalidated so it'll get redrawn.*/ + if ( (0 == model_getCurrentMoveCount( board->model, newPlayer )) + != (0 == model_getCurrentMoveCount( board->model, oldPlayer )) ) { + model_foreachPrevCell( board->model, boardCellChanged, board ); + } + + /* Just in case somebody started a trade when it wasn't his turn and + there were plenty of tiles but now there aren't. */ + if ( board->tradeInProgress[newPlayer] && + server_countTilesInPool(board->server) < MIN_TRADE_TILES ) { + board->tradeInProgress[newPlayer] = XP_FALSE; + board->traySelBits[newPlayer] = 0x00; /* clear any selected */ + } + + invalTradeWindow( board, oldPlayer, + board->tradeInProgress[newPlayer] ); + + invalArrowCell( board, XP_FALSE ); + board->selPlayer = (XP_U8)newPlayer; + invalArrowCell( board, XP_FALSE ); + + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + + setTrayVisState( board, TRAY_REVERSED ); + } + board->scoreBoardInvalid = XP_TRUE; /* if only one player, number of + tiles remaining may have changed*/ +} /* board_selectPlayer */ + +void +board_hiliteCellAt( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Rect cellRect; + + if ( getCellRect( board, col, row, &cellRect ) ) { + draw_invertCell( board->draw, &cellRect ); + invalCell( board, col, row, XP_FALSE ); + } + /* sleep(1); */ +} /* board_hiliteCellAt */ + +void +board_resetEngine( BoardCtxt* board ) +{ + server_resetEngine( board->server, board->selPlayer ); +} /* board_resetEngine */ + +/* Find a rectangle either centered on the board or pinned to the point at + * which the mouse went down. + */ +static void +positionMiniWRect( BoardCtxt* board, XP_Rect* rect, XP_Bool center ) +{ + if ( center ) { + XP_Rect boardBounds = board->boardBounds; + rect->top = boardBounds.top + ((boardBounds.height - rect->height)/2); + rect->left = boardBounds.left + ((boardBounds.width - rect->width)/2); + } else { + XP_S16 x = board->penDownX; + XP_S16 y = board->penDownY; + + if ( board->leftHanded ) { + rect->left = (x + rect->width <= board->boardBounds.width)? + x : x - rect->width; + } else { + rect->left = x - rect->width > 0? x - rect->width : x; + } + rect->top = XP_MAX( board->boardBounds.top + 1, y - rect->height ); + } +} /* positionBonusRect */ + +static void +timerFiredForPen( BoardCtxt* board ) +{ + XP_UCHAR* text = (XP_UCHAR*)NULL; + XP_UCHAR buf[64]; + + if ( board->penDownObject == OBJ_BOARD ) { + XP_U16 col, row; + XWBonusType bonus; + + XP_ASSERT( !board->penTimerFired ); + + coordToCell( board, board->penDownX, board->penDownY, &col, + &row ); + bonus = util_getSquareBonus(board->util, board->model, col, row); + if ( bonus != BONUS_NONE ) { + text = draw_getMiniWText( board->draw, (XWMiniTextType)bonus ); + } + board->penTimerFired = XP_TRUE; + + } else if ( board->penDownObject == OBJ_SCORE ) { + XP_S16 player; + LocalPlayer* lp; + player = figureScorePlayerTapped( board, board->penDownX, + board->penDownY ); + /* I've seen this assert fire on simulator. No log is kept so I can't + tell why, but might want to test and do nothing in this case. */ + /* XP_ASSERT( player >= 0 ); */ + if ( player >= 0 ) { + lp = &board->gi->players[player]; + + text = emptyStringIfNull(lp->name); + + if ( !lp->isLocal ) { + XP_UCHAR* format = util_getUserString(board->util, + STR_NONLOCAL_NAME); + XP_SNPRINTF( buf, sizeof(buf), format, text ); + text = buf; + } + } + + board->penTimerFired = XP_TRUE; + } + + if ( !!text ) { + MiniWindowStuff* stuff = &board->miniWindowStuff[MINIWINDOW_VALHINT]; + makeMiniWindowForText( board, text, MINIWINDOW_VALHINT ); + XP_ASSERT( stuff->text == text ); + draw_drawMiniWindow(board->draw, text, &stuff->rect, + &stuff->closure); + } +} /* timerFiredForPen */ + +static void +setTimerIf( BoardCtxt* board ) +{ + XP_Bool timerWanted = board->gi->timerEnabled && !board->gameOver; + + if ( timerWanted && !board->timerPending ) { + util_setTimer( board->util, TIMER_TIMERTICK ); + board->timerPending = XP_TRUE; + } +} /* setTimerIf */ + +static void +timerFiredForTimer( BoardCtxt* board ) +{ + board->timerPending = XP_FALSE; + if ( !board->gameOver ) { + XP_S16 turn = server_getCurrentTurn( board->server ); + + if ( turn >= 0 ) { + ++board->gi->players[turn].secondsUsed; + + if ( turn == board->selPlayer ) { + drawTimer( board ); + } + } + } + setTimerIf( board ); +} /* timerFiredForTimer */ + +void +board_timerFired( BoardCtxt* board, XWTimerReason why ) +{ + if ( why == TIMER_PENDOWN ) { + timerFiredForPen( board ); + } else { + XP_ASSERT( why == TIMER_TIMERTICK ); + timerFiredForTimer( board ); + } +} /* board_timerFired */ + +void +board_pushTimerSave( BoardCtxt* board ) +{ + if ( board->gi->timerEnabled ) { + if ( board->timerSaveCount++ == 0 ) { + board->timerStoppedTime = util_getCurSeconds( board->util ); +#ifdef DEBUG + board->timerStoppedTurn = server_getCurrentTurn( board->server ); +#endif + } + } +} /* board_pushTimerSave */ + +void +board_popTimerSave( BoardCtxt* board ) +{ + if ( board->gi->timerEnabled ) { + + /* it's possible for the count to be 0, if e.g. the timer was enabled + between calls to board_pushTimerSave and this call, as can happen on + franklin. So that's not an error. */ + if ( board->timerSaveCount > 0 ) { + XP_S16 turn = server_getCurrentTurn( board->server ); + + XP_ASSERT( board->timerStoppedTurn == turn ); + + if ( --board->timerSaveCount == 0 && turn >= 0 ) { + XP_U32 curTime = util_getCurSeconds( board->util ); + XP_U32 elapsed; + + XP_ASSERT( board->timerStoppedTime != 0 ); + elapsed = curTime - board->timerStoppedTime; + XP_LOGF( "board_popTimerSave: elapsed=%ld", elapsed ); + XP_ASSERT( elapsed <= 0xFFFF ); + board->gi->players[turn].secondsUsed += (XP_U16)elapsed; + } + } + } +} /* board_popTimerSave */ + +void +board_invalAll( BoardCtxt* board ) +{ + XP_U16 lastRow = model_numRows( board->model ); + while ( lastRow-- ) { + board->redrawFlags[lastRow] = ~0; + } + board->needsDrawing = XP_TRUE; + + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + board->tradingMiniWindowInvalid = XP_TRUE; + board->scoreBoardInvalid = XP_TRUE; +} /* board_invalAll */ + +/* + * invalidate all cells that contain a tile. Return TRUE if any invalidation + * actually happens. + */ +static XP_Bool +invalCellsWithTiles( BoardCtxt* board, XP_Bool doMirror ) +{ + ModelCtxt* model = board->model; + XP_S16 turn = board->selPlayer; + XP_Bool includePending = board->trayVisState == TRAY_REVEALED; + XP_S16 col, row; + + /* for each col,row pair, invalidate it if it holds a tile that's visible + AND if the rect itself is visible */ + for ( row = model_numRows( model )-1; row >= 0; --row ) { + for ( col = model_numCols( model )-1; col >= 0; --col ) { + Tile tile; + XP_Bool ignore; + if ( model_getTile( model, col, row, includePending, + turn, &tile, &ignore, &ignore, &ignore ) ) { + + if ( board->boardObscuresTray ) { + XP_Rect cellR; + if ( !getCellRect( board, col, row, &cellR ) + || (board->trayVisState != TRAY_HIDDEN && + rectContainsRect( &board->trayBounds, &cellR )) ) { + continue; + } + } + invalCell( board, col, row, doMirror ); + } + } + } + return board->needsDrawing; +} /* invalCellsWithTiles */ + +static void +checkScrollCell( void* p_board, XP_U16 turn, XP_U16 col, XP_U16 row ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + XP_Rect rect; + + if ( board->boardObscuresTray && board->trayVisState != TRAY_HIDDEN ) { + + while ( !getCellRect( board, col, row, &rect ) ) { + XP_U16 oldOffset = board_getYOffset( board ); + if ( rect.top < board->boardBounds.top ) { + --oldOffset; + } else if ( rect.top + rect.height > + board->boardBounds.top + board->boardBounds.height ) { + ++oldOffset; + } else { + XP_ASSERT( 0 ); + } + board_setYOffset( board, oldOffset, XP_TRUE ); + } + } +} /* checkScrollCell */ + +/* if any of a blank's neighbors is invalid, so must the blank become (since + * they share a border and drawing the neighbor will redraw the blank's border + * too) We'll want to redraw only those blanks that are themselves already + * invalid *OR* that become invalid this way, and so we'll build a new + * BlankQueue of them and replace the old. + * + * I'm not sure what happens if two blanks are neighbors. + */ +#define INVAL_BIT_SET(b,c,r) (((b)->redrawFlags[(r)] & (1 <<(c))) != 0) +static void +invalBlanksWithNeighbors( BoardCtxt* board, BlankQueue* bqp ) +{ + ModelCtxt* model = board->model; + XP_U16 i; + XP_U16 lastCol, lastRow; + BlankQueue invalBlanks; + XP_U16 nInvalBlanks = 0; + + lastCol = model_numCols(model) - 1; + lastRow = model_numRows(model) - 1; + + for ( i = 0; i < bqp->nBlanks; ++i ) { + XP_U16 col = bqp->col[i]; + XP_U16 row = bqp->row[i]; + + if ( INVAL_BIT_SET( board, col, row ) + || (col > 0 && INVAL_BIT_SET( board, col-1, row )) + || (col < lastCol && INVAL_BIT_SET( board, col+1, row )) + || (row > 0 && INVAL_BIT_SET( board, col, row-1 )) + || (row < lastRow && INVAL_BIT_SET( board, col, row+1 )) ) { + + invalCell( board, col, row, XP_FALSE ); + + invalBlanks.col[nInvalBlanks] = (XP_U8)col; + invalBlanks.row[nInvalBlanks] = (XP_U8)row; + ++nInvalBlanks; + } + } + invalBlanks.nBlanks = nInvalBlanks; + XP_MEMCPY( bqp, &invalBlanks, sizeof(*bqp) ); +} /* invalBlanksWithNeighbors */ + +static void +scrollIfCan( BoardCtxt* board ) +{ + if ( board->yOffset != board->prevYScrollOffset ) { + XP_Rect scrollR = board->boardBounds; + XP_Bool scrolled; + XP_S16 dist; + + invalSelTradeWindow( board ); + dist = (board->yOffset - board->prevYScrollOffset) + * board->boardVScale; + + scrolled = draw_vertScrollBoard( board->draw, &scrollR, dist ); + + if ( scrolled ) { + /* inval the rows that have been scrolled into view. I'm cheating + making the client figure the inval rect, but Palm's the only + client now and it does it so well.... */ + invalCellsUnderRect( board, &scrollR, XP_FALSE ); + } else { + board_invalAll( board ); + } + + board->prevYScrollOffset = board->yOffset; + } +} /* scrollIfCan */ + +static void +drawTradeWindowIf( BoardCtxt* board ) +{ + if ( board->tradingMiniWindowInvalid && + TRADE_IN_PROGRESS(board) && board->trayVisState == TRAY_REVEALED ) { + MiniWindowStuff* stuff; + + makeMiniWindowForTrade( board ); + + stuff = &board->miniWindowStuff[MINIWINDOW_TRADING]; + draw_drawMiniWindow( board->draw, stuff->text, + &stuff->rect, (void**)NULL ); + + board->tradingMiniWindowInvalid = XP_FALSE; + } +} /* drawTradeWindowIf */ + +XP_Bool +board_draw( BoardCtxt* board ) +{ + drawScoreBoard( board ); + + if ( board->needsDrawing ) { + XP_Bool allDrawn = XP_TRUE; + XP_S16 lastCol, lastRow, i; + ModelCtxt* model = board->model; + BlankQueue bq; + + draw_boardBegin( board->draw, &board->boardBounds, + board->focussed == OBJ_BOARD ); + + scrollIfCan( board ); /* this must happen before we count blanks + since it invalidates squares */ + + model_listPlacedBlanks( model, board->selPlayer, + board->trayVisState == TRAY_REVEALED, &bq ); + invalBlanksWithNeighbors( board, &bq ); + + lastRow = model_numRows( model ); + while ( lastRow-- ) { + XP_U16 rowFlags = board->redrawFlags[lastRow]; + if ( rowFlags != 0 ) { + XP_U16 colMask; + XP_U16 failedBits = 0; + lastCol = model_numCols( model ); + for ( colMask = 1<<(lastCol-1); lastCol--; colMask >>= 1 ) { + if ( (rowFlags & colMask) != 0 ) { + if ( !drawCell( board, lastCol, lastRow, XP_TRUE ) ) { + failedBits |= colMask; + allDrawn = XP_FALSE; + } + } + } + board->redrawFlags[lastRow] = failedBits; + } + } + + /* draw the blanks we skipped before */ + for ( i = 0; i < bq.nBlanks; ++i ) { + if ( !drawCell( board, bq.col[i], bq.row[i], XP_FALSE ) ) { + allDrawn = XP_FALSE; + } + } + + if ( board->trayVisState == TRAY_REVEALED ) { + XP_Rect cursorRect; + BoardArrow* arrow = &board->boardArrow[board->selPlayer]; + + if ( arrow->visible ) { + XP_Bool drawVertical = + (arrow->vert == XP_CURSOR_KEY_DOWN) ^ board->isFlipped; + if ( getCellRect( board, arrow->col, arrow->row, + &cursorRect ) ) { + XWBonusType bonus; + bonus = util_getSquareBonus( board->util, model, arrow->col, + arrow->row ); + draw_drawBoardArrow( board->draw, &cursorRect, + bonus, drawVertical ); + } + } +#ifdef KEYBOARD_NAV + { + BdCursorLoc loc = board->bdCursor[board->selPlayer]; + if ( getCellRect( board, loc.col, loc.row, &cursorRect ) ) { + draw_drawBoardCursor( board->draw, &cursorRect ); + } + } +#endif + + } + + drawTradeWindowIf( board ); + + draw_boardFinished( board->draw ); + + board->needsDrawing = !allDrawn; + } + + drawTray( board, board->focussed==OBJ_TRAY ); + + return !board->needsDrawing; +} /* board_draw */ + +static XP_S16 +figureSecondsLeft( BoardCtxt* board ) +{ + CurGameInfo* gi = board->gi; + XP_U16 secondsUsed = gi->players[board->selPlayer].secondsUsed; + XP_U16 secondsAvailable = gi->gameSeconds / gi->nPlayers; + XP_ASSERT( gi->timerEnabled ); + return secondsAvailable - secondsUsed; +} /* figureSecondsLeft */ + +static void +drawTimer( BoardCtxt* board ) +{ + if ( board->gi->timerEnabled ) { + XP_S16 secondsLeft = figureSecondsLeft( board ); + + draw_drawTimer( board->draw, &board->timerBounds, &board->timerBounds, + board->selPlayer, secondsLeft ); + } +} /* drawTimer */ + +typedef struct DrawScoreData { + DrawScoreInfo dsi; + XP_U16 height; + XP_U16 width; +} DrawScoreData; + +static void +drawScoreBoard( BoardCtxt* board ) +{ + if ( board->scoreBoardInvalid ) { + short i; + + XP_U16 nPlayers = board->gi->nPlayers; + + if ( nPlayers > 0 ) { + ModelCtxt* model = board->model; + XP_S16 curTurn = server_getCurrentTurn( board->server ); + XP_U16 selPlayer = board->selPlayer; + XP_S16 nTilesInPool = server_countTilesInPool( board->server ); + XP_Rect scoreRect = board->scoreBdBounds; + XP_S16* adjustDim; + XP_S16* adjustPt; + XP_U16 totalDim, extra, nShares, remWidth, remHeight, remDim; + DrawScoreData* dp; + DrawScoreData datum[MAX_NUM_PLAYERS]; + XP_S16 scores[MAX_NUM_PLAYERS]; + XP_Bool isVertical = !board->scoreSplitHor; + + draw_scoreBegin( board->draw, &board->scoreBdBounds, nPlayers, + board->focussed==OBJ_SCORE ); + + /* Let platform decide whether the rem: string should be given any + space once there are no tiles left. On Palm that space is + clickable to drop a menu, so will probably leave it. */ + draw_measureRemText( board->draw, &board->scoreBdBounds, + nTilesInPool, &remWidth, &remHeight ); + remDim = isVertical? remHeight : remWidth; + + if ( isVertical ) { + adjustPt = &scoreRect.top; + adjustDim = &scoreRect.height; + } else { + adjustPt = &scoreRect.left; + adjustDim = &scoreRect.width; + } + + /* Get the scores from the model or by calculating them based on + the end-of-game state. */ + if ( board->gameOver ) { + model_figureFinalScores( model, scores, (XP_S16*)NULL ); + } else { + for ( i = 0; i < nPlayers; ++i ) { + scores[i] = model_getPlayerScore( model, i ); + } + } + + totalDim = remDim; + + /* figure spacing for each scoreboard entry */ + for ( dp = datum, i = 0; i < nPlayers; ++i, ++dp ) { + LocalPlayer* lp = &board->gi->players[i]; + + dp->dsi.score = scores[i]; + dp->dsi.isTurn = (i == curTurn); + dp->dsi.name = emptyStringIfNull(lp->name); + dp->dsi.selected = board->trayVisState != TRAY_HIDDEN + && i==selPlayer; + dp->dsi.isRobot = lp->isRobot; + dp->dsi.isRemote = !lp->isLocal; + dp->dsi.nTilesLeft = (nTilesInPool > 0)? -1: + model_getNumTilesTotal( model, i ); + draw_measureScoreText( board->draw, &scoreRect, + &dp->dsi, &dp->width, &dp->height ); + totalDim += isVertical ? dp->height : dp->width; + } + + /* break extra space into chunks, one to follow REM and another to + preceed the timer, and then one for each player. Generally the + player's score will be centered in the rect it's given, so in + effect we're putting half the chunk on either side. The goal + here is for the scores to be closer to each other than they are + to the rem: string and timer on the ends. */ + nShares = nPlayers; + if ( remDim > 0 ) { + ++nShares; + } + XP_ASSERT( *adjustDim >= totalDim ); + extra = (*adjustDim - totalDim) / nShares; + + /* at this point, the scoreRect should be anchored at the + scoreboard rect's upper left. */ + + if ( remDim > 0 ) { + *adjustDim = remDim; + + draw_drawRemText( board->draw, &scoreRect, &scoreRect, + nTilesInPool ); + + remDim += extra; + *adjustPt += remDim; + } + + XP_ASSERT( remDim <= 0x00FF ); + board->remDim = (XP_U8)remDim; /* save now so register can be + reused */ + for ( dp = datum, i = 0; i < nPlayers; ++dp, ++i ) { + XP_Rect innerRect; + XP_U16 dim = isVertical? dp->height:dp->width; + *adjustDim = board->scoreDims[i] = dim + extra; + + innerRect.width = dp->width; + innerRect.height = dp->height; + innerRect.left = scoreRect.left + + ((scoreRect.width - innerRect.width) / 2); + innerRect.top = scoreRect.top + + ((scoreRect.height - innerRect.height) / 2); + + draw_score_drawPlayer( board->draw, i, &innerRect, &scoreRect, + &dp->dsi ); + *adjustPt += *adjustDim; + } + + draw_scoreFinished( board->draw ); + } + + board->scoreBoardInvalid = XP_FALSE; + } + + drawTimer( board ); +} /* drawScoreBoard */ + +void +board_setTrayLoc( BoardCtxt* board, XP_U16 left, XP_U16 top, + XP_U8 trayScaleH, XP_U8 trayScaleV, + XP_U8 dividerWidth ) +{ + board->trayBounds.left = left; + board->trayBounds.top = top; + /* what's this +1 for? */ + board->trayBounds.width = (trayScaleH * MAX_TRAY_TILES) + 1; + board->trayBounds.height = trayScaleV; + + board->trayScaleH = trayScaleH; + board->trayScaleV = trayScaleV; + + board->dividerWidth = dividerWidth; + board->trayBounds.width += (XP_U8)dividerWidth; + + /* boardObscuresTray is about whether they *can* overlap, not just about + * they do given the current scroll position of the board. Remember + * (e.g. for curses version) that vertical intersection isn't enough.*/ + board->boardObscuresTray = + (top < (board->boardBounds.top + + board->boardVScale * model_numRows( board->model ))) + && (left < (board->boardBounds.left + + board->boardHScale * model_numCols( board->model ))); + if ( !board->boardObscuresTray && board->trayVisState == TRAY_HIDDEN ) { + XW_TrayVisState state = TRAY_REVERSED; + setTrayVisState( board, state ); + } + figureBoardRect( board ); +} /* board_setTrayLoc */ + +static void +invalCellsUnderRect( BoardCtxt* board, XP_Rect* rect, XP_Bool doMirror ) +{ + XP_U16 lastCol, lastRow; + + lastRow = model_numRows( board->model ); + while ( lastRow-- ) { + lastCol = model_numCols( board->model ); + while ( lastCol-- ) { + XP_Rect cell; + + if ( getCellRect( board, lastCol, lastRow, &cell ) && + rectsIntersect( rect, &cell ) ) { + invalCell( board, lastCol, lastRow, doMirror ); + } + } + } +} /* invalCellsUnderRect */ + +void +board_invalRect( BoardCtxt* board, XP_Rect* rect ) +{ + if ( rectsIntersect( rect, &board->boardBounds ) ) { + invalCellsUnderRect( board, rect, XP_FALSE ); + } + + if ( rectsIntersect( rect, &board->trayBounds ) ) { + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + } + + if ( rectsIntersect( rect, &board->scoreBdBounds ) ) { + board->scoreBoardInvalid = XP_TRUE; + } +} /* board_invalRect */ + +/* When the tray's hidden, check if it overlaps where the board wants to be, + * and adjust the board's rect if needed so that hit-testing will work + * "through" where the tray used to be. + * + * Visible here means different things depending on whether the tray can + * overlap the board. If not, then we never "hide" the tray; rather, we turn + * it over. So figure out what hidden means and then change the state if it's + * not already there. + * + * But remember that revealing the tray in an overlapping situation is a + * two-step process. First anyone can cause the tray to be drawn over the top + * of the board. But then a second gesture is required to cause the tray to + * be drawn with tiles face-up. + */ +XP_Bool +board_hideTray( BoardCtxt* board ) +{ + XW_TrayVisState soughtState; + if ( board->boardObscuresTray ) { + soughtState = TRAY_HIDDEN; + } else { + soughtState = TRAY_REVERSED; + } + return setTrayVisState( board, soughtState ); +} /* board_hideTray */ + +static XP_S16 +chooseBestSelPlayer( BoardCtxt* board ) +{ + ServerCtxt* server = board->server; + + if ( board->gameOver ) { + return board->selPlayer; + } else { + + XP_S16 curTurn = server_getCurrentTurn( server ); + + if ( curTurn >= 0 ) { + XP_U16 nPlayers = board->gi->nPlayers; + XP_U16 i; + + for ( i = 0; i < nPlayers; ++i ) { + LocalPlayer* lp = &board->gi->players[curTurn]; + + if ( !lp->isRobot && lp->isLocal ) { + return curTurn; + } + curTurn = (curTurn + 1) % nPlayers; + } + } + } + + return -1; +} /* chooseBestSelPlayer */ + +/* Reveal the tray of the currently selected player. If that's a robot other + * code should flag the error. + */ +XP_Bool +board_showTray( BoardCtxt* board ) +{ + return checkRevealTray( board ); +} /* board_showTray */ + +XW_TrayVisState +board_getTrayVisState( BoardCtxt* board ) +{ + return board->trayVisState; +} /* board_getTrayVisible */ + +static XP_Bool +setTrayVisState( BoardCtxt* board, XW_TrayVisState newState ) +{ + XP_Bool changed; + + if ( newState == TRAY_REVERSED && board->gameOver ) { + newState = TRAY_REVEALED; + } + + changed = board->trayVisState != newState; + if ( changed ) { + XP_Bool nowHidden = newState == TRAY_HIDDEN; + XP_U16 selPlayer = board->selPlayer; + + /* redraw cells that are pending; whether tile is visible may + change */ + model_foreachPendingCell( board->model, selPlayer, + boardCellChanged, board ); + + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + + board->trayVisState = newState; + + invalSelTradeWindow( board ); + + figureBoardRect( board ); /* comes before setYOffset since that + uses rects to calc scroll */ + + if ( board->boardObscuresTray ) { + if ( nowHidden ) { + board->preHideYOffset = board_getYOffset( board ); + board_setYOffset( board, 0, XP_FALSE ); + } else { + board_setYOffset( board, board->preHideYOffset, XP_FALSE ); + } + } + + if ( nowHidden ) { + /* Can't always set this to TRUE since in obscuring case tray will + * get erased after the cells that are supposed to be revealed + * get drawn. */ + board->eraseTray = !board->boardObscuresTray; + invalCellsUnderRect( board, &board->trayBounds, XP_FALSE ); + } + invalArrowCell( board, XP_FALSE ); + board->scoreBoardInvalid = XP_TRUE; /* b/c what's bold may change */ + + util_trayHiddenChange( board->util, board->trayVisState ); + } + return changed; +} /* setTrayVisState */ + +XP_Bool +board_flip( BoardCtxt* board ) +{ + invalArrowCell( board, XP_TRUE ); +#ifdef KEYBOARD_NAV + invalCursorCell( board, XP_TRUE ); +#endif + + if ( board->boardObscuresTray ) { + invalCellsUnderRect( board, &board->trayBounds, XP_TRUE ); + } + invalCellsWithTiles( board, XP_TRUE ); + + board->isFlipped = !board->isFlipped; + + return board->needsDrawing; + /* invalCellsWithTiles won't work here because we need to invalidate + * cells that are empty, or that will be empty after the flip. */ + /* board_invalAll( board ); */ + /* return XP_TRUE; */ +} /* board_flip */ + +XP_Bool +board_toggle_showValues( BoardCtxt* board ) +{ + board->showCellValues = !board->showCellValues; + return invalCellsWithTiles( board, XP_FALSE ); +} /* board_toggle_showValues */ + +XP_Bool +board_setShowColors( BoardCtxt* board, XP_Bool showColors ) +{ + board->showColors = showColors; + board->scoreBoardInvalid = XP_TRUE; + return invalCellsWithTiles( board, XP_FALSE ); +} /* board_setShowColors */ + +#if 0 +XP_Bool +board_getShowColors( BoardCtxt* board ) +{ + return board->showColors; +} /* board_getShowColors */ +#endif + +XP_Bool +board_replaceTiles( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + while ( replaceLastTile( board ) ) { + result = XP_TRUE; + } + + return result; +} /* board_replaceTiles */ + +/* There are a few conditions that must be true for any of several actions + to be allowed. Check them here. */ +static XP_Bool +preflight( BoardCtxt* board ) +{ + return !board->gameOver && !TRADE_IN_PROGRESS(board) + && server_getCurrentTurn(board->server) >= 0 + && checkRevealTray( board ); +} /* preflight */ + +/* Refuse with error message if any tiles are currently on board in this turn. + * Then call the engine, and display the first move. Return true if there's + * any redrawing to be done. + */ +XP_Bool +board_requestHint( BoardCtxt* board, XP_Bool* workRemainsP ) +{ + MoveInfo newMove; + XP_Bool result = XP_FALSE; + XP_S16 nTiles; + const Tile* tiles; + XP_U16 selPlayer; + EngineCtxt* engine; + XP_Bool searchComplete = XP_TRUE; + + *workRemainsP = XP_FALSE; /* in case we exit without calling engine */ + + selPlayer = board->selPlayer; + engine = server_getEngineFor( board->server, selPlayer ); + /* engine may be null, if e.g. hint menu's chosen for a remote player */ + result = !!engine && !board->gi->hintsNotAllowed && preflight( board ); + + if ( result ) { + const TrayTileSet* tileSet; + ModelCtxt* model = board->model; + + /* undo any current move. otherwise we won't pass the full tray to + the engine. Would it be better, though, to pass the whole tray + regardless where its contents are? */ + if ( model_getCurrentMoveCount( model, selPlayer ) > 0 ) { + model_resetCurrentTurn( model, selPlayer ); + board_draw( board ); + } + + tileSet = model_getPlayerTiles( model, selPlayer ); + nTiles = tileSet->nTiles - board->dividerLoc[selPlayer]; + result = nTiles > 0; + if ( result ) { + XP_Bool wasVisible; + XP_Bool canMove; + + wasVisible = setArrowVisible( board, XP_FALSE ); + + (void)board_replaceTiles( board ); + + tiles = tileSet->tiles + board->dividerLoc[selPlayer]; + + board_pushTimerSave( board ); + + searchComplete = engine_findMove(engine, model, + model_getDictionary(model), + tiles, nTiles, NO_SCORE_LIMIT, + &canMove, &newMove ); + board_popTimerSave( board ); + + if ( searchComplete && canMove ) { + model_makeTurnFromMoveInfo( model, selPlayer, &newMove); + } else { + XP_STATUSF( "unable to complete hint request\n" ); + } + *workRemainsP = !searchComplete; + + /* hide the cursor if it's been obscured by the new move */ + if ( wasVisible ) { + XP_U16 col, row; + + getArrow( board, &col, &row ); + if ( cellOccupied( board, col, row, XP_TRUE ) ) { + wasVisible = XP_FALSE; + } + setArrowVisible( board, wasVisible ); + } + } + } + return result; +} /* board_requestHint */ + +static XP_Bool +drawCell( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Bool skipBlanks ) +{ + XP_Rect cellRect; + Tile tile; + XP_UCHAR ch[4]; + XP_Bool isBlank, isEmpty, showPending, recent, pending = XP_FALSE; + XWBonusType bonus; + ModelCtxt* model = board->model; + DictionaryCtxt* dict = model_getDictionary( model ); + XP_U16 selPlayer = board->selPlayer; + XP_Bool showPrevMove = + (0 == model_getCurrentMoveCount( model, selPlayer )); + + if ( dict != NULL && getCellRect( board, col, row, &cellRect ) ) { + + /* This 'while' is only here so I can 'break' below */ + while ( board->trayVisState == TRAY_HIDDEN || + !rectContainsRect( &board->trayBounds, &cellRect ) ) { + XP_S16 owner = -1; + XP_Bool invert = XP_FALSE; + XP_Bitmap bitmap = NULL; + XP_UCHAR* textP = (XP_UCHAR*)ch; + + showPending = board->trayVisState == TRAY_REVEALED; + + isEmpty = !model_getTile( model, col, row, showPending, + selPlayer, &tile, &isBlank, + &pending, &recent ); + + *(unsigned long*)&ch[0] = 0L; + if ( isEmpty ) { + isBlank = XP_FALSE; + } else if ( isBlank && skipBlanks ) { + break; + } else { + if ( board->showColors ) { + owner = (XP_S16)model_getCellOwner( model, col, row ); + } + + invert = pending || (showPrevMove && recent); + + if ( board->showCellValues ) { + Tile valTile = isBlank? dict_getBlankTile( dict ) : tile; + XP_U16 val = dict_getTileValue( dict, valTile ); + XP_SNPRINTF( ch, sizeof(ch), (XP_UCHAR*)"%d", val ); + } else if ( dict_faceIsBitmap( dict, tile ) ) { + bitmap = dict_getFaceBitmap( dict, tile, XP_FALSE ); + textP = (XP_UCHAR*)NULL; + } else { + dict_tilesToString( dict, &tile, 1, ch ); + } + } + bonus = util_getSquareBonus( board->util, model, col, row ); + return draw_drawCell( board->draw, &cellRect, textP, bitmap, + owner, bonus, isBlank, invert, + (isEmpty && (col==board->star_row) + && (row==board->star_row))); + } + } + + return XP_TRUE; +} /* drawCell */ + +static void +figureBoardRect( BoardCtxt* board ) +{ + XP_Rect boardBounds = board->boardBounds; + + boardBounds.width = model_numCols( board->model ) * board->boardHScale; + boardBounds.height = (model_numRows( board->model ) - board->yOffset) + * board->boardVScale; + + if ( board->trayVisState != TRAY_HIDDEN && board->boardObscuresTray ) { + boardBounds.height = board->trayBounds.top - boardBounds.top - 1; + } + + board->boardBounds = boardBounds; +} /* figureBoardRect */ + +static void +coordToCell( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_U16* colP, + XP_U16* rowP ) +{ + XP_U16 col, row, max; + + x -= board->boardBounds.left; + + y -= board->boardBounds.top; + y += board->boardVScale * board->yOffset; + + col = x / board->boardHScale; + row = y / board->boardVScale; + + + if ( board->isFlipped ) { + XP_U16 tmp = col; + col = row; + row = tmp; + } + + max = model_numCols( board->model ) - 1; + /* I don't deal with non-square boards yet. */ + XP_ASSERT( max + 1 == model_numRows( board->model ) ); + if ( col > max ) { + col = max; + } + if ( row > max ) { + row = max; + } + + *colP = col; + *rowP = row; +} /* coordToCell */ + +static XP_Bool +getCellRect( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Rect* rect ) +{ + XP_S16 top; + XP_Bool onBoard = XP_TRUE; + + if ( board->isFlipped ) { + XP_U16 tmp = col; + col = row; + row = tmp; + } + + if ( row < board->yOffset ) { + onBoard = XP_FALSE; + } + + rect->left = board->boardBounds.left + (col * board->boardHScale); + top = board->boardBounds.top + + ((row - board->yOffset) * board->boardVScale); + if ( top >= (board->boardBounds.top + board->boardBounds.height) ) { + onBoard = XP_FALSE; + } + rect->top = top; + + rect->width = board->boardHScale; + rect->height = board->boardVScale; + return onBoard; +} /* getCellRect */ + +static void +invalCell( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Bool doMirror ) +{ + board->redrawFlags[row] |= 1 << col; + + if ( doMirror ) { + board->redrawFlags[col] |= 1 << row; + } + + /* if the trade window is up and this cell intersects it, set up to draw + it again */ + if ( (board->trayVisState != TRAY_HIDDEN) && TRADE_IN_PROGRESS(board) ) { + XP_Rect rect; + if ( getCellRect( board, col, row, &rect ) ) { /* cell's visible */ + XP_Rect windowR = board->miniWindowStuff[MINIWINDOW_TRADING].rect; + if ( rectsIntersect( &rect, &windowR ) ) { + board->tradingMiniWindowInvalid = XP_TRUE; + } + } + } + + board->needsDrawing = XP_TRUE; +} /* invalCell */ + +#ifdef KEYBOARD_NAV +static XP_Bool +focusNext( BoardCtxt* board ) +{ + short numHolders = 3; /* board */ + short tmp = (short)board->focussed; /* avoid franklin casting crap */ + + tmp %= numHolders; + board->focussed = (BoardObjectType)(tmp + 1); /* skip OBJ_NONE */ + + return numHolders > 1; +} /* focusNext */ +#endif + +#ifdef POINTER_SUPPORT +static XP_Bool +pointOnSomething( BoardCtxt* board, XP_U16 x, XP_U16 y, BoardObjectType* wp ) +{ + if ( board->trayVisState != TRAY_HIDDEN + && rectContainsPt( &board->trayBounds, x, y ) ) { + *wp = OBJ_TRAY; + } else if ( rectContainsPt( &board->boardBounds, x, y ) ) { + *wp = OBJ_BOARD; + } else if ( rectContainsPt( &board->scoreBdBounds, x, y ) ) { + *wp = OBJ_SCORE; + } else { + return XP_FALSE; + } + + return XP_TRUE; +} /* pointOnSomething */ +#endif + +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +/* Move the given tile to the board. If it's a blank, we need to ask the user + * what to call it first. + */ +XP_Bool +moveTileToArrowLoc( BoardCtxt* board, XP_U8 index ) +{ + XP_Bool result; + BoardArrow* arrow = &board->boardArrow[board->selPlayer]; + if ( arrow->visible ) { + result = moveTileToBoard( board, arrow->col, arrow->row, + (XP_U16)index, EMPTY_TILE ); + if ( result ) { + advanceArrow( board ); + } + } else { + result = XP_FALSE; + } + return result; +} /* moveTileToArrowLoc */ + +static void +makeMiniWindowForText( BoardCtxt* board, XP_UCHAR* text, + MiniWindowType winType ) +{ + XP_Rect rect; + MiniWindowStuff* stuff = &board->miniWindowStuff[winType]; + + draw_measureMiniWText( board->draw, text, + (XP_U16*)&rect.width, + (XP_U16*)&rect.height ); + positionMiniWRect( board, &rect, winType == MINIWINDOW_TRADING ); + stuff->rect = rect; + stuff->text = text; +} /* makeMiniWindowForText */ + +static void +makeMiniWindowForTrade( BoardCtxt* board ) +{ + XP_UCHAR* text; + + text = draw_getMiniWText( board->draw, INTRADE_MW_TEXT ); + + makeMiniWindowForText( board, text, MINIWINDOW_TRADING ); +} /* makeMiniWindowForTrade */ + +XP_Bool +board_beginTrade( BoardCtxt* board ) +{ + XP_Bool result; + + result = preflight( board ); + if ( result ) { + /* check turn before tradeInProgress so I can't tell my opponent's in a + trade */ + if ( 0 != model_getCurrentMoveCount( board->model, board->selPlayer )){ + util_userError( board->util, ERR_CANT_TRADE_MID_MOVE ); + } else { + if ( server_countTilesInPool(board->server) < MIN_TRADE_TILES){ + util_userError( board->util, ERR_TOO_FEW_TILES_LEFT_TO_TRADE ); + } else { + board->tradingMiniWindowInvalid = XP_TRUE; + board->needsDrawing = XP_TRUE; + board->tradeInProgress[board->selPlayer] = XP_TRUE; + setArrowVisible( board, XP_FALSE ); + result = XP_TRUE; + } + } + } + return result; +} /* board_beginTrade */ + +#ifdef POINTER_SUPPORT +static XP_Bool +ptOnTradeWindow( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Rect* windowR = &board->miniWindowStuff[MINIWINDOW_TRADING].rect; + return rectContainsPt( windowR, x, y ); +} /* ptOnTradeWindow */ + +static XP_Bool +handlePenDownOnBoard( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + /* Start a timer no matter what. After it fires we'll decide whether it's + appropriate to handle it. No. That's too expensive */ + if ( TRADE_IN_PROGRESS(board) && ptOnTradeWindow( board, x, y ) ) { + return XP_FALSE; + } + util_setTimer( board->util, TIMER_PENDOWN ); + + return XP_FALSE; +} /* handlePenDownOnBoard */ +#endif + +/* If there's a password, ask it; if they match, change the state of the tray + * to TRAY_REVEALED. Return value talks about whether the tray needs to be + * redrawn, not the success of the password compare. + */ +static XP_Bool +askRevealTray( BoardCtxt* board ) +{ + XP_Bool revealed = XP_FALSE; + XP_U16 selPlayer = board->selPlayer; + LocalPlayer* lp = &board->gi->players[selPlayer]; + + if ( board->gameOver ) { + revealed = XP_TRUE; + } else if ( server_getCurrentTurn( board->server ) < 0 ) { + revealed = XP_FALSE; +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( !lp->isLocal ) { + util_userError( board->util, ERR_NO_PEEK_REMOTE_TILES ); +#endif + } else if ( lp->isRobot ) { + util_userError( board->util, ERR_NO_PEEK_ROBOT_TILES ); + } else { + revealed = !player_hasPasswd( lp ); + + if ( !revealed ) { + XP_UCHAR buf[16]; + XP_U16 buflen = sizeof(buf); + XP_UCHAR* name = emptyStringIfNull(lp->name); + + /* repeat until player gets passwd right or hits cancel */ + while ( util_askPassword( board->util, name, buf, &buflen ) ) { + if ( buflen > 0 ) { + if ( player_passwordMatches( lp, (XP_U8*)buf, buflen ) ) { + revealed = XP_TRUE; + break; + } + } + } + } + } + + if ( revealed ) { + setTrayVisState( board, TRAY_REVEALED ); + } + return revealed; +} /* askRevealTray */ + +XP_Bool +checkRevealTray( BoardCtxt* board ) +{ + XP_Bool result = board->trayVisState == TRAY_REVEALED; + if ( !result ) { + result = askRevealTray( board ); + } + return result; +} /* checkRevealTray */ + +#ifdef POINTER_SUPPORT +XP_Bool +board_handlePenDown( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Time when ) +{ + XP_Bool result = XP_FALSE; + BoardObjectType onWhich; + + if ( !pointOnSomething( board, x, y, &onWhich ) ) { + board->penDownObject = OBJ_NONE; + return XP_FALSE; + } + + switch ( onWhich ) { + + case OBJ_BOARD: + result = handlePenDownOnBoard( board, x, y ); + break; + + case OBJ_TRAY: + /* XP_ASSERT( board->trayIsVisible ); */ + XP_ASSERT( board->trayVisState != TRAY_HIDDEN ); + + if ( board->trayVisState == TRAY_REVERSED ) { + result = askRevealTray( board ); + } else { + result = handlePenDownInTray( board, x, y ); + } + break; + + case OBJ_SCORE: + if ( figureScorePlayerTapped( board, x, y ) >= 0 ) { + util_setTimer( board->util, TIMER_PENDOWN ); + } + break; + default: + break; + } + + board->penDownX = x; + board->penDownY = y; + board->penDownTime = when; + board->penDownObject = onWhich; + /* board->inDrag = XP_TRUE; */ + + return result; /* no redraw needed */ +} /* board_handlePenDown */ +#endif + +XP_Bool +board_handlePenMove( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool result = XP_FALSE; + + if ( board->tileDragState.dragInProgress ) { + result = continueTileDrag( board, x, y ) != 0; + } else if ( board->divDragState.dragInProgress ) { + result = continueDividerDrag( board, x, y ) != 0; + } + + return result; +} /* board_handlePenMove */ + +static XP_S16 +figureScorePlayerTapped( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_S16 result = -1; + XP_S16 left; + XP_U16 nPlayers = board->gi->nPlayers; + + if ( board->scoreSplitHor ) { + left = x - board->scoreBdBounds.left; + } else { + left = y - board->scoreBdBounds.top; + } + + left -= board->remDim; + if ( left >= 0 ) { + for ( result = 0; result < nPlayers; ++result ) { + if ( left < board->scoreDims[result] ) { + break; + } + left -= board->scoreDims[result]; + } + } + if ( result >= nPlayers ) { + result = -1; + } + return result; +} /* figureScorePlayerTapped */ + +/* If the pen also went down on the scoreboard, make the selected player the + * one closest to the mouse up loc. + */ +#ifdef POINTER_SUPPORT +static XP_Bool +handlePenUpScore( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool result = XP_FALSE; + + if ( board->penDownObject == OBJ_SCORE ) { + + XP_S16 playerNum = figureScorePlayerTapped( board, x, y ); + + if ( playerNum >= 0 ) { + board_selectPlayer( board, playerNum ); + + result = XP_TRUE; + } + } + return result; +} /* handlePenUpScore */ +#endif + +/* Called when user taps on the board and a tray tile's selected. + */ +static XP_Bool +moveSelTileToBoardXY( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Bool result; + XP_U16 selPlayer = board->selPlayer; + TileBit bits = board->traySelBits[selPlayer]; + XP_U16 tileIndex; + + if ( bits == NO_TILES ) { + return XP_FALSE; + } + + tileIndex = indexForBits( bits ); + + result = tileIndex < model_getNumTilesInTray( board->model, selPlayer ) + && moveTileToBoard( board, col, row, tileIndex, EMPTY_TILE ); + + if ( result ) { + XP_U16 leftInTray; + + leftInTray = model_getNumTilesInTray( board->model, selPlayer ); + if ( leftInTray == 0 ) { + bits = NO_TILES; + } else if ( leftInTray <= tileIndex ) { + bits = 1 << (tileIndex-1); + board_invalTrayTiles( board, bits ); + } + board->traySelBits[selPlayer] = bits; + } + + return result; +} /* moveSelTileToBoardXY */ + +static XP_Bool +cellOccupied( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Bool inclPending ) +{ + Tile tile; + XP_Bool ignr; + + XP_Bool result = model_getTile( board->model, col, row, inclPending, + board->selPlayer, &tile, + &ignr, &ignr, &ignr ); + return result; +} /* cellOccupied */ + +/* If I tap on the arrow, transform it. If I tap in an empty square where + * it's not, move it there, making it visible if it's not already. + */ +static XP_Bool +tryMoveArrow( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Bool result = XP_FALSE; + + if ( !cellOccupied( board, col, row, + board->trayVisState == TRAY_REVEALED ) ) { + + BoardArrow* arrow = &board->boardArrow[board->selPlayer]; + + if ( arrow->visible && arrow->col == col && arrow->row == row ) { + /* change it; if vertical, hide; else if horizontal make + vertical */ + if ( arrow->vert ) { + arrow->visible = XP_FALSE; + } else { + arrow->vert = XP_TRUE; + } + } else { + /* move it/reveal it */ + if ( !arrow->visible && !board->disableArrow ) { + arrow->visible = XP_TRUE; + arrow->vert = XP_FALSE; + } else { + invalArrowCell( board, XP_FALSE ); + } + arrow->col = (XP_U8)col; + arrow->row = (XP_U8)row; + } + invalCell( board, col, row, XP_FALSE ); + result = XP_TRUE; + } + return result; +} /* tryMoveArrow */ + +/* Did I tap on a tile on the board that I have not yet committed? If so, + * return it to the tray. + */ +static XP_Bool +tryReplaceTile( BoardCtxt* board, XP_U16 pencol, XP_U16 penrow ) +{ + XP_Bool result = XP_FALSE; + XP_S16 index; + XP_U16 col, row; + Tile tile; + XP_Bool ignore, isPending; + + if ( model_getTile( board->model, pencol, penrow, XP_TRUE, + board->selPlayer, &tile, &ignore, &isPending, + (XP_Bool*)NULL ) + && isPending ) { + + XP_S16 count = model_getCurrentMoveCount( board->model, + board->selPlayer ); + while ( count-- ) { + index = count; + model_getCurrentMoveTile( board->model, board->selPlayer, + &index, &tile, &col, &row, &ignore ); + if ( col == pencol && row == penrow ) { + model_moveBoardToTray( board->model, board->selPlayer, + index ); + /* the cursor should show up where the tile used to be so it's + easy to replace it. */ + setArrow( board, col, row ); + result = XP_TRUE; + break; + } + } + } + return result; +} /* tryReplaceTile */ + +static XP_Bool +handleActionInCell( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + return moveSelTileToBoardXY( board, col, row ) + || tryMoveArrow( board, col, row ) + || tryReplaceTile( board, col, row ); +} /* handleActionInCell */ +#endif /* POINTER_SUPPORT || KEYBOARD_NAV */ + +#ifdef POINTER_SUPPORT +XP_Bool +board_handlePenUp( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Time when ) +{ + XP_Bool result = XP_FALSE; + + if ( board->tileDragState.dragInProgress ) { + result = endTileDrag( board, x, y ); + } else if ( board->divDragState.dragInProgress ) { + result = endDividerDrag( board, x, y ); + } else if ( board->penTimerFired ) { + if ( valHintMiniWindowActive( board ) ) { + hideMiniWindow( board, XP_TRUE, MINIWINDOW_VALHINT ); + result = XP_TRUE; + } + board->penTimerFired = XP_FALSE; + } else { + BoardObjectType onWhich; + if ( pointOnSomething( board, x, y, &onWhich ) ) { + + switch( onWhich ) { + case OBJ_SCORE: + result = handlePenUpScore( board, x, y ); + break; + case OBJ_BOARD: + if ( board->penDownObject == OBJ_BOARD + && board->trayVisState == TRAY_REVEALED ) { + + if ( TRADE_IN_PROGRESS(board) ) { + if ( ptOnTradeWindow( board, x, y )) { + XP_U16 selPlayer = board->selPlayer; + invalSelTradeWindow( board ); + board->tradeInProgress[selPlayer] = XP_FALSE; + board_invalTrayTiles( + board, board->traySelBits[selPlayer] ); + board->traySelBits[selPlayer] = 0x00; + result = XP_TRUE; + } + } else { + XP_U16 col, row; + coordToCell( board, board->penDownX, board->penDownY, + &col, &row ); + result = handleActionInCell( board, col, row ); + } + } + break; + case OBJ_TRAY: + break; + default: + XP_ASSERT( XP_FALSE ); + } + } + } + + board->penDownObject = OBJ_NONE; + return result; +} /* board_handlePenUp */ +#endif /* #ifdef POINTER_SUPPORT */ + +#ifdef KEYBOARD_NAV +static XP_Key +flipKey( BoardCtxt* board, XP_Key key ) { + if ( board->isFlipped ) { + switch( key ) { + case XP_CURSOR_KEY_DOWN: + return XP_CURSOR_KEY_RIGHT; + case XP_CURSOR_KEY_UP: + return XP_CURSOR_KEY_LEFT; + case XP_CURSOR_KEY_LEFT: + return XP_CURSOR_KEY_UP; + case XP_CURSOR_KEY_RIGHT: + return XP_CURSOR_KEY_DOWN; + default: + XP_ASSERT(0); + } + } + return key; +} /* flipKey */ +#endif + +XP_Bool +board_handleKey( BoardCtxt* board, XP_Key key ) +{ + XP_Bool result = XP_FALSE; + XP_Bool trayVisible = board->trayVisState == TRAY_REVEALED; + + switch( key ) { +#ifdef KEYBOARD_NAV + case XP_CURSOR_KEY_DOWN: + case XP_CURSOR_KEY_UP: + case XP_CURSOR_KEY_LEFT: + case XP_CURSOR_KEY_RIGHT: + if ( board->focussed == OBJ_BOARD ) { + result = trayVisible && board_moveCursor( board, + flipKey(board,key) ); + } else if ( board->focussed == OBJ_SCORE ) { + result = moveScoreCursor( board, key ); + } else if ( trayVisible && board->focussed == OBJ_TRAY ) { + result = tray_moveCursor( board, key ); + } + break; +#endif + + case XP_CURSOR_KEY_DEL: + if ( trayVisible ) { + replaceLastTile( board ); + result = XP_TRUE; + } + break; + +#ifdef KEYBOARD_NAV + case XP_FOCUSCHANGE_KEY: + if ( focusNext( board ) ) { + board_invalAll( board ); + result = XP_TRUE; + } + break; + + case XP_RETURN_KEY: + if ( board->focussed == OBJ_TRAY ) { + result = tray_keyAction( board ); + } else if ( board->focussed == OBJ_BOARD ) { + /* mimic pen-down/pen-up on cursor */ + BdCursorLoc loc = board->bdCursor[board->selPlayer]; + result = handleActionInCell( board, loc.col, loc.row ); + } else if ( board->focussed == OBJ_SCORE ) { + /* tap on what's already selected: reveal tray, etc. */ + board_selectPlayer( board, board->selPlayer ); + result = XP_TRUE; + } + break; +#endif + + default: + XP_ASSERT( key >= XP_KEY_LAST ); + + result = trayVisible && moveKeyTileToBoard( board, key ); + if ( result ) { + advanceArrow( board ); + } + } /* switch */ + + return result; +} /* board_handleKey */ + +/* used by curses version only! */ +#ifdef KEYBOARD_NAV +XP_Bool +board_toggle_arrowDir( BoardCtxt* board ) +{ + BoardArrow* arrow = &board->boardArrow[board->selPlayer]; + if ( arrow->visible ) { + arrow->vert = !arrow->vert; + invalArrowCell( board, XP_FALSE ); + return XP_TRUE; + } else { + return XP_FALSE; + } +} /* board_toggle_cursorDir */ + +static XP_Bool +moveScoreCursor( BoardCtxt* board, XP_Key key ) +{ + XP_Bool result = XP_TRUE; + XP_U16 nPlayers = board->gi->nPlayers; + XP_U16 selPlayer = board->selPlayer + nPlayers; + + switch ( key ) { + case XP_CURSOR_KEY_DOWN: + case XP_CURSOR_KEY_RIGHT: + ++selPlayer; + break; + case XP_CURSOR_KEY_UP: + case XP_CURSOR_KEY_LEFT: + --selPlayer; + break; + default: + result = XP_FALSE; + } + board_selectPlayer( board, selPlayer % nPlayers ); + return result; +} /* moveScoreCursor */ +#endif + +static void +advanceArrow( BoardCtxt* board ) +{ + XP_Bool vertical = board->boardArrow[board->selPlayer].vert; + + XP_Key key = (vertical ^ board->isFlipped)? + XP_CURSOR_KEY_DOWN: XP_CURSOR_KEY_RIGHT; + + XP_ASSERT( board->trayVisState == TRAY_REVEALED ); + + board_moveArrow( board, key, XP_FALSE ); +} /* advanceArrow */ + +static XP_Bool +figureNextLoc( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle, + XP_Bool avoidOccupied, XP_U16* colP, XP_U16* rowP ) +{ + XP_S16 max; + XP_S16* useWhat; + XP_S16 end = 0; + XP_U16 counter = 0; + XP_S16 incr = 0; + XP_U16 numCols, numRows; + XP_Bool result = XP_FALSE; + + /* XP_ASSERT( board->focussed == OBJ_BOARD ); */ + /* don't allow cursor's jumps to reveal hidden tiles */ + if ( board->trayVisState != TRAY_REVEALED || cursorKey == XP_KEY_NONE ) { + return XP_FALSE; + } + + numRows = model_numRows( board->model ); + numCols = model_numCols( board->model ); + + switch ( cursorKey ) { + + case XP_CURSOR_KEY_DOWN: + incr = 1; + useWhat = (XP_S16*)rowP; + max = numRows; + end = max; + break; + case XP_CURSOR_KEY_UP: + incr = -1; + useWhat = (XP_S16*)rowP; + max = numRows; + end = -1; + break; + case XP_CURSOR_KEY_LEFT: + incr = -1; + useWhat = (XP_S16*)colP; + max = numCols; + end = -1; + break; + case XP_CURSOR_KEY_RIGHT: + incr = 1; + useWhat = (XP_S16*)colP; + max = numCols; + end = max; + break; + default: + XP_ASSERT( XP_FALSE ); + return XP_FALSE; + } + + XP_ASSERT( incr != 0 ); + + for ( counter = max; ; --counter ) { + + *useWhat += incr; + + if ( (counter == 0) || (!canCycle && (*useWhat == end)) ) { + result = XP_FALSE; + break; + } + + *useWhat = (*useWhat + max) % max; + + if ( !avoidOccupied + || !cellOccupied( board, *colP, *rowP, XP_TRUE ) ) { + result = XP_TRUE; + break; + } + } + + return result; +} /* figureNextLoc */ + +static XP_Bool +board_moveArrow( BoardCtxt* board, XP_Key cursorKey, XP_Bool canCycle ) +{ + XP_U16 col, row; + XP_Bool changed; + + setArrowVisible( board, XP_TRUE ); + (void)getArrow( board, &col, &row ); + changed = figureNextLoc( board, cursorKey, canCycle, XP_TRUE, &col, &row ); + if ( changed ) { + (void)setArrow( board, col, row ); + } + return changed; +} /* board_moveArrow */ + +#ifdef KEYBOARD_NAV +static XP_Bool +board_moveCursor( BoardCtxt* board, XP_Key cursorKey ) +{ + BdCursorLoc loc = board->bdCursor[board->selPlayer]; + XP_U16 col = loc.col; + XP_U16 row = loc.row; + XP_Bool changed; + + changed = figureNextLoc( board, cursorKey, XP_TRUE, XP_FALSE, + &col, &row ); + if ( changed ) { + invalCell( board, loc.col, loc.row, XP_FALSE ); + invalCell( board, col, row, XP_FALSE ); + loc.col = col; + loc.row = row; + board->bdCursor[board->selPlayer] = loc; + + XP_LOGF( "moved cursor to %d,%d", col, row ); + } + return changed; +} /* board_moveCursor */ +#endif + +static XP_Bool +rectContainsRect( XP_Rect* rect1, XP_Rect* rect2 ) +{ + return ( rect1->top <= rect2->top + && rect1->left <= rect2->left + && rect1->top + rect1->height >= rect2->top + rect2->height + && rect1->left + rect1->width >= rect2->left + rect2->width ); +} /* rectContainsRect */ + +XP_Bool +rectContainsPt( XP_Rect* rect, XP_U16 x, XP_U16 y ) +{ + /* 7/4 Made <= into <, etc., because a tap on the right boundary of the + board was still mapped onto the board but dividing by scale put it in + the 15th column. If this causes other problems and the '=' chars have + to be put back then deal with that, probably by forcing an + out-of-bounds col/row to the nearest possible. */ + return ( rect->top <= y + && rect->left <= x + && rect->top + rect->height >= y + && rect->left + rect->width >= x ); +} /* rectContainsPt */ + +static XP_Bool +rectsIntersect(XP_Rect* rectP1, XP_Rect* rectP2) +{ + XP_Rect rect1 = *rectP1; + XP_Rect rect2 = *rectP2; + + if ( rect1.top >= rect2.top + rect2.height ) { + return XP_FALSE; + } else if ( rect1.left >= rect2.left + rect2.width ) { + return XP_FALSE; + } else if ( rect2.top >= rect1.top + rect1.height ) { + return XP_FALSE; + } else if ( rect2.left >= rect1.left + rect1.width ) { + return XP_FALSE; + } else { + return XP_TRUE; + } +} /* rectsIntersect */ + +static XP_Bool +replaceLastTile( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + XP_U16 tilesInMove = model_getCurrentMoveCount( board->model, + board->selPlayer ); + + if ( tilesInMove > 0 ) { + XP_U16 col, row; + Tile tile; + XP_Bool isBlank; + XP_S16 index; + + index = -1; + model_getCurrentMoveTile( board->model, board->selPlayer, &index, + &tile, &col, &row, &isBlank ); + + model_moveBoardToTray( board->model, board->selPlayer, index ); + + setArrow( board, col, row ); + + result = XP_TRUE; + } + + return result; +} /* replaceLastTile */ + +static XP_Bool +moveTileToBoard( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_U16 tileIndex, + Tile blankFace ) +{ + if ( cellOccupied( board, col, row, XP_TRUE ) ) { + return XP_FALSE; + } + + model_moveTrayToBoard( board->model, board->selPlayer, col, row, + tileIndex, blankFace ); + + return XP_TRUE; +} /* moveTileToBoard */ + +static XP_Bool +moveKeyTileToBoard( BoardCtxt* board, XP_Key cursorKey ) +{ + /* keep compiler happy: assign defaults */ + Tile tile, blankFace; + XP_U16 col, row; + DictionaryCtxt* dict = model_getDictionary( board->model ); + XP_S16 turn = board->selPlayer; + XP_S16 tileIndex; + XP_UCHAR buf[2]; + + /* Is there a cursor at all? */ + if ( !getArrow( board, &col, &row ) ) { + return XP_FALSE; + } + + XP_ASSERT( !TRADE_IN_PROGRESS(board) ); + + /* Figure out if we have the tile in the tray */ + buf[0] = cursorKey; + buf[1] = '\0'; + tile = dict_tileForString( dict, buf ); + if ( tile == EMPTY_TILE ) { /* not found in dict */ + return XP_FALSE; + } + + tileIndex = model_trayContains( board->model, turn, tile ); + if ( tileIndex >= 0 ) { + blankFace = EMPTY_TILE; /* will be ignored */ + } else { + Tile blankTile = dict_getBlankTile( dict ); + tileIndex = model_trayContains( board->model, turn, blankTile ); + if ( tileIndex >= 0 ) { /* there's a blank for it */ + blankFace = tile; + } else { + return XP_FALSE; + } + } + + return moveTileToBoard( board, col, row, tileIndex, blankFace ); +} /* moveKeyTileToBoard */ + +static void +setArrowFor( BoardCtxt* board, XP_U16 player, XP_U16 col, XP_U16 row ) +{ + BoardArrow* arrow = &board->boardArrow[player]; + invalCell( board, arrow->col, arrow->row, XP_FALSE ); + invalCell( board, col, row, XP_FALSE ); + + arrow->col = (XP_U8)col; + arrow->row = (XP_U8)row; + + checkScrollCell( board, player, col, row ); +} /* setArrowFor */ + +static void +setArrow( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + setArrowFor( board, board->selPlayer, col, row ); +} /* setArrow */ + +static XP_Bool +getArrowFor( BoardCtxt* board, XP_U16 player, XP_U16* col, XP_U16* row ) +{ + BoardArrow* arrow = &board->boardArrow[player]; + *col = arrow->col; + *row = arrow->row; + return arrow->visible; +} /* getArrowFor */ + +static XP_Bool +getArrow( BoardCtxt* board, XP_U16* col, XP_U16* row ) +{ + return getArrowFor( board, board->selPlayer, col, row ); +} /* getArrow */ + +static XP_Bool +setArrowVisible( BoardCtxt* board, XP_Bool visible ) +{ + return setArrowVisibleFor( board, board->selPlayer, visible ); +} /* setArrowVisible */ + +static XP_Bool +setArrowVisibleFor( BoardCtxt* board, XP_U16 player, XP_Bool visible ) +{ + BoardArrow* arrow = &board->boardArrow[player]; + XP_Bool result = arrow->visible; + if ( arrow->visible != visible ) { + arrow->visible = visible; + invalArrowCell( board, XP_FALSE ); + } + return result; +} /* setArrowVisibleFor */ + +/***************************************************************************** + * Listener callbacks + ****************************************************************************/ +static void +boardCellChanged( void* p_board, XP_U16 turn, XP_U16 col, XP_U16 row, + XP_Bool added ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + XP_Bool pending, found, ignoreBlank; + Tile ignoreTile; + XP_U16 ccol, crow; + + /* for each player, check if the tile overwrites the cursor */ + + found = model_getTile( board->model, col, row, XP_TRUE, turn, + &ignoreTile, &ignoreBlank, &pending, (XP_Bool*)NULL ); + + XP_ASSERT( !added || found ); /* if added is true so must found be */ + + if ( !added && !found ) { + /* nothing to do */ + } else { + XP_U16 player, nPlayers; + + nPlayers = board->gi->nPlayers; + + /* This is a bit gross. It's an attempt to combine two loops. In one + case, we've added a tile (tentative move, say) and need to hide the + cursor for the player who added the tile. In the second, we're + changing the state of a tile (from tentative to permanent, say) and + now need to worry about the players who turn it _isn't_ but whose + cursor may be in the wrong place. This latter case happens a lot + in multi-device games when a turn shows up from elsewhere and plops + down on the board. */ + for ( player = 0; player < nPlayers; ++player ) { + if ( (added && (!pending || turn == board->selPlayer)) + || (found && turn != board->selPlayer) ) { + if ( getArrowFor( board, player, &ccol, &crow ) + && ( (ccol == col) && (crow == row) ) ) { + setArrowVisibleFor( board, player, XP_FALSE ); + } + } + } + + checkScrollCell( board, turn, col, row ); + } + + invalCell( (BoardCtxt*)p_board, col, row, XP_FALSE ); +} /* boardCellChanged */ + +static void +boardTileChanged( void* p_board, XP_U16 turn, TileBit bits ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + if ( turn == board->selPlayer ) { + board_invalTrayTiles( board, bits ); + } +} /* boardTileChanged */ + +static void +boardTurnChanged( void* p_board ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + XP_S16 nextPlayer; + + XP_ASSERT( board->timerSaveCount == 0 ); + + board->gameOver = XP_FALSE; + + nextPlayer = chooseBestSelPlayer( board ); + if ( nextPlayer >= 0 ) { + board_selectPlayer( board, nextPlayer ); + } + + setTimerIf( board ); + + board->scoreBoardInvalid = XP_TRUE; +} /* boardTurnChanged */ + +static void +boardGameOver( void* closure ) +{ + BoardCtxt* board = (BoardCtxt*)closure; + board->scoreBoardInvalid = XP_TRUE; /* not sure if this will do it. */ + board->gameOver = XP_TRUE; + util_notifyGameOver( board->util ); +} /* boardGameOver */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/board.h b/xwords4/common/board.h new file mode 100644 index 000000000..cd2ae6894 --- /dev/null +++ b/xwords4/common/board.h @@ -0,0 +1,163 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef __BOARD_H__ +#define __BOARD_H__ + +#include "comtypes.h" +#include "model.h" +#include "server.h" +#include "draw.h" +#include "xwstream.h" + +/* typedef struct BoardVTable { */ +/* } BoardVTable; */ + +#ifdef CPLUS +extern "C" { +#endif + +typedef enum { + TRAY_HIDDEN, /* doesn't happen unless tray overlaps board */ + TRAY_REVERSED, + TRAY_REVEALED +} XW_TrayVisState; + +typedef enum { + OBJ_NONE, + OBJ_BOARD, + OBJ_SCORE, + OBJ_TRAY +} BoardObjectType; + +typedef enum { + /* keep these three together: for the cursor */ + XP_KEY_NONE, + XP_CURSOR_KEY_DOWN, + XP_CURSOR_KEY_RIGHT, + + XP_CURSOR_KEY_UP, + XP_CURSOR_KEY_LEFT, + XP_CURSOR_KEY_DEL, + XP_FOCUSCHANGE_KEY, + XP_RETURN_KEY, + + XP_KEY_LAST +} XP_Key; + +#define BONUS_HINT_INTERVAL 15 /* stolen from xwords.c */ + +/* typedef struct BoardCtxt BoardCtxt; */ + + +BoardCtxt* board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, + DrawCtx* draw, XW_UtilCtxt* util ); +BoardCtxt* board_makeFromStream( MPFORMAL XWStreamCtxt* stream, + ModelCtxt* model, ServerCtxt* server, + DrawCtx* draw, XW_UtilCtxt* util, + XP_U16 nPlayers ); + +void board_destroy( BoardCtxt* board ); + +void board_writeToStream( BoardCtxt* board, XWStreamCtxt* stream ); + +void board_setPos( BoardCtxt* board, XP_U16 left, XP_U16 top, + XP_Bool leftHanded ); +void board_reset( BoardCtxt* board ); + +/* Vertical scroll support; offset is in rows, not pixels */ +XP_Bool board_setYOffset( BoardCtxt* board, XP_U16 newOffset, + XP_Bool invalRevealed ); +XP_U16 board_getYOffset( BoardCtxt* board ); + +void board_setScoreboardLoc( BoardCtxt* board, + XP_U16 scoreLeft, XP_U16 scoreTop, + XP_U16 scoreWidth, XP_U16 scoreHeight, + XP_Bool divideHorizontally ); +void board_setTimerLoc( BoardCtxt* board, + XP_U16 timerLeft, XP_U16 timerTop, + XP_U16 timerWidth, XP_U16 timerHeight ); +void board_invalAll( BoardCtxt* board ); +void board_invalRect( BoardCtxt* board, XP_Rect* rect ); + +XP_Bool board_draw( BoardCtxt* board ); + +XP_Bool board_flip( BoardCtxt* board ); +XP_Bool board_toggle_showValues( BoardCtxt* board ); +XP_Bool board_getShowColors( BoardCtxt* board ); +XP_Bool board_setShowColors( BoardCtxt* board, XP_Bool showColors ); +XP_Bool board_replaceTiles( BoardCtxt* board ); + +XP_Bool board_requestHint( BoardCtxt* board, XP_Bool* workRemainsP ); + +void board_setScale( BoardCtxt* board, XP_U16 hScale, XP_U16 vScale ); +void board_getScale( BoardCtxt* board, XP_U16* hScale, XP_U16* vScale ); + +XP_Bool board_prefsChanged( BoardCtxt* board, CommonPrefs* cp ); + +BoardObjectType board_getFocusOwner( BoardCtxt* board ); + +void board_hiliteCellAt( BoardCtxt* board, XP_U16 col, XP_U16 row ); + +void board_resetEngine( BoardCtxt* board ); +void board_timerFired( BoardCtxt* board, XWTimerReason why ); + +XP_Bool board_commitTurn( BoardCtxt* board ); + +void board_pushTimerSave( BoardCtxt* board ); +void board_popTimerSave( BoardCtxt* board ); + +#ifdef POINTER_SUPPORT +XP_Bool board_handlePenDown( BoardCtxt* board, XP_U16 x, XP_U16 y, + XP_Time when ); +XP_Bool board_handlePenMove( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool board_handlePenUp( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Time when ); +#endif + +XP_Bool board_handleKey( BoardCtxt* board, XP_Key key ); + +#ifdef KEYBOARD_NAV +/* void board_focusChange( BoardCtxt* board ); */ +XP_Bool board_toggle_arrowDir( BoardCtxt* board ); +#endif + +/******************** Tray methods ********************/ +#define NO_TILES ((TileBit)0) + +void board_setTrayLoc( BoardCtxt* board, XP_U16 trayLeft, XP_U16 trayTop, + XP_U8 trayScaleH, XP_U8 trayScaleV, + XP_U8 dividerWidth ); +XP_Bool board_hideTray( BoardCtxt* board ); +XP_Bool board_showTray( BoardCtxt* board ); +XW_TrayVisState board_getTrayVisState( BoardCtxt* board ); + +void board_invalTrayTiles( BoardCtxt* board, TileBit what ); +XP_Bool board_juggleTray( BoardCtxt* board ); +XP_Bool board_beginTrade( BoardCtxt* board ); + +#if defined FOR_GREMLINS || defined KEYBOARD_NAV +XP_Bool board_moveDivider( BoardCtxt* board, XP_Bool right ); +#endif + + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/boardp.h b/xwords4/common/boardp.h new file mode 100644 index 000000000..3044edf91 --- /dev/null +++ b/xwords4/common/boardp.h @@ -0,0 +1,191 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _BOARDP_H_ +#define _BOARDP_H_ + +#include "comtypes.h" +#include "model.h" +#include "board.h" +#include "mempool.h" /* debug only */ + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct TileDragState { + XP_Bool dragInProgress; + + XP_Bool wasHilited; + TileBit selectionAtStart; + XP_Bool movePending; + TileBit prevIndex; +} TileDragState; + +typedef struct DividerDragState { + XP_Bool dragInProgress; +} DividerDragState; + +typedef struct BoardArrow { /* gets flipped along with board */ + XP_U8 col; + XP_U8 row; + XP_Bool vert; + XP_Bool visible; +} BoardArrow; + +#ifdef KEYBOARD_NAV +typedef struct BdCursorLoc { + XP_U8 col; + XP_U8 row; +} BdCursorLoc; +#endif + +/* We only need two of these, one for the value hint and the other for the + trading window. There's never more than of the former since it lives only + as long as the pen is down. There are, in theory, as many trading windows + as there are (local) players, but they can all use the same window. */ +typedef struct MiniWindowStuff { + void* closure; + unsigned char* text; + XP_Rect rect; +} MiniWindowStuff; + +enum { MINIWINDOW_VALHINT, MINIWINDOW_TRADING }; +typedef XP_U16 MiniWindowType; /* one of the two above */ + +struct BoardCtxt { +/* BoardVTable* vtable; */ + ModelCtxt* model; + ServerCtxt* server; + DrawCtx* draw; + XW_UtilCtxt* util; + + struct CurGameInfo* gi; + + XP_U16 boardHScale; + XP_U16 boardVScale; + XP_U16 yOffset; + XP_U16 preHideYOffset; + XP_U16 prevYScrollOffset; /* represents where the last draw took place; + used to see if bit scrolling can be used */ + XP_U16 penDownX; + XP_U16 penDownY; + + XP_U32 timerStoppedTime; + XP_U16 timerSaveCount; +#ifdef DEBUG + XP_S16 timerStoppedTurn; +#endif + + XP_U16 redrawFlags[MAX_ROWS]; + + XP_Rect boardBounds; + + XP_Time penDownTime; + BoardObjectType penDownObject; + + XP_Bool needsDrawing; + XP_Bool isFlipped; + XP_Bool showGrid; + XP_Bool gameOver; + XP_Bool leftHanded; + XP_Bool badWordRejected; + XP_Bool timerPending; + XP_Bool disableArrow; + + XP_Bool tradeInProgress[MAX_NUM_PLAYERS]; + XP_Bool eraseTray; + XP_Bool boardObscuresTray; + XP_Bool scoreSplitHor; /* how to divide the scoreboard? */ + + XP_U16 star_row; + + /* Unless KEYBOARD_NAV is defined, this does not change */ + BoardObjectType focussed; + + BoardArrow boardArrow[MAX_NUM_PLAYERS]; +#ifdef KEYBOARD_NAV + BdCursorLoc bdCursor[MAX_NUM_PLAYERS]; +#endif + XP_U8 dividerLoc[MAX_NUM_PLAYERS]; /* 0 means left of 0th tile, etc. */ + + XP_U8 scoreDims[MAX_NUM_PLAYERS]; + + /* scoreboard state */ + XP_Rect scoreBdBounds; + XP_Rect timerBounds; + XP_U8 selPlayer; /* which player is selected (!= turn) */ + XP_U8 remDim; /* width (or ht) of the "rem:" string in scoreboard */ + + /* tray state */ + XP_U8 trayScaleH; + XP_U8 trayScaleV; + XP_Rect trayBounds; + XP_U8 dividerWidth; /* 0 would mean invisible */ + XP_Bool dividerInvalid; + + XP_Bool scoreBoardInvalid; + TileDragState tileDragState; + DividerDragState divDragState; + + MiniWindowStuff miniWindowStuff[2]; + XP_Bool tradingMiniWindowInvalid; + + TileBit trayInvalBits; + TileBit traySelBits[MAX_NUM_PLAYERS]; +#ifdef KEYBOARD_NAV + XP_U8 trayCursorLoc[MAX_NUM_PLAYERS]; +#endif + + XW_TrayVisState trayVisState; + XP_Bool penTimerFired; + XP_Bool showCellValues; + XP_Bool showColors; + + MPSLOT +}; + +#define valHintMiniWindowActive( board ) \ + ((XP_Bool)((board)->miniWindowStuff[MINIWINDOW_VALHINT].text != NULL)) +#define MY_TURN(b) ((b)->selPlayer == server_getCurrentTurn( (b)->server )) +#define TRADE_IN_PROGRESS(b) ((b)->tradeInProgress[(b)->selPlayer]==XP_TRUE) + +/* tray-related functions */ +XP_Bool handlePenDownInTray( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool handlePenUpTray( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Time when ); +void drawTray( BoardCtxt* board, XP_Bool focussed ); +TileBit continueTileDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool endTileDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool continueDividerDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool endDividerDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool moveTileToArrowLoc( BoardCtxt* board, XP_U8 index ); +XP_U16 indexForBits( XP_U8 bits ); +XP_Bool rectContainsPt( XP_Rect* rect1, XP_U16 x, XP_U16 y ); +XP_Bool checkRevealTray( BoardCtxt* board ); + +#ifdef KEYBOARD_NAV +XP_Bool tray_moveCursor( BoardCtxt* board, XP_Key cursorKey ); +XP_Bool tray_keyAction( BoardCtxt* board ); +#endif + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/commmgr.h b/xwords4/common/commmgr.h new file mode 100644 index 000000000..5c431a501 --- /dev/null +++ b/xwords4/common/commmgr.h @@ -0,0 +1,54 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +/* The Communications manager brokers messages between controllers It + * maps players to devices. + * + * Messages for players go through it. If the player is remote, then the + * message is queued. If local, it's a simple function call executed + * immediately. When the caller is finished, it calls comms_okToSend() + * or somesuch, and all the queued messages are combined into a single + * message for each device represented, and sent. + * + * The problem of duplicate messages: Say there are two players A and B on + * remote device D. A has just made a move {{7,6,'A'},{7,7,'T'}}. The + * server wants to tell each player about A's move. It will want to send the + * same message to every player but A, yet there's no point in sending to B's + * device since the information is already there. + * + * There are three possiblities: put message codes into classes -- e.g. + */ + +#ifndef _COMMMGR_H_ +#define _COMMMGR_H_ + +#include "comtypes.h" /* that's *common* types */ +#include "xwstream.h" +#include "server.h" + +/* typedef struct CommMgrCtxt CommMgrCtxt; */ + +CommMgrCtxt* commmgr_make( XWStreamCtxt* serverStream ); +void commmgr_setServer( CommMgrCtxt* commMgr, ServerCtxt* server ); + +XWStreamCtxt* commmgr_getServerStream( CommMgrCtxt* commmgr ); + +void commmgr_receiveMessage( CommMgrCtxt* commmgr, XWStreamCtxt* incomming ); + +#endif diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c new file mode 100644 index 000000000..381a3a3f1 --- /dev/null +++ b/xwords4/common/comms.c @@ -0,0 +1,719 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifdef USE_STDIO +# include +#endif + +#include "comms.h" + +#include "util.h" +#include "xwstream.h" +#include "memstream.h" + +#define cEND 0x65454e44 + +#ifndef XWFEATURE_STANDALONE_ONLY + +typedef struct MsgQueueElem { + struct MsgQueueElem* next; + XP_U8* msg; + XP_U16 len; + XP_U16 channelNo; + MsgID msgID; +} MsgQueueElem; + +typedef struct AddressRecord { + struct AddressRecord* next; + CommsAddrRec addr; +#ifdef DEBUG + XP_U16 lastACK; + XP_U16 nUniqueBytes; +#endif + MsgID nextMsgID; /* on a per-channel basis */ + MsgID lastMsgReceived; /* on a per-channel basis */ + XP_PlayerAddr channelNo; +} AddressRecord; + +struct CommsCtxt { + XW_UtilCtxt* util; + + XP_U32 connID; /* 0 means ignore; otherwise must match */ + XP_U16 nextChannelNo; + AddressRecord* recs; /* return addresses */ + + TransportSend sendproc; + void* sendClosure; + + MsgQueueElem* msgQueueHead; + MsgQueueElem* msgQueueTail; + XP_U16 queueLen; + + /* added to stream format. Must deal with format change in saved + games. PENDING */ + XP_U16 listenPort; + CommsAddrRec addr; + + XP_Bool isServer; +#ifdef DEBUG + XP_U16 nUniqueBytes; +#endif + MPSLOT +}; + +/**************************************************************************** + * prototypes + ****************************************************************************/ +static AddressRecord* rememberChannelAddress( CommsCtxt* comms, + XP_PlayerAddr channelNo, + CommsAddrRec* addr ); +static XP_Bool channelToAddress( CommsCtxt* comms, XP_PlayerAddr channelNo, + CommsAddrRec** addr ); +static AddressRecord* getRecordFor( CommsCtxt* comms, + XP_PlayerAddr channelNo ); +static XP_S16 sendMsg( CommsCtxt* comms, MsgQueueElem* elem ); +static void addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem ); +static XP_U16 countAddrRecs( CommsCtxt* comms ); + +/**************************************************************************** + * implementation + ****************************************************************************/ +CommsCtxt* +comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer, + TransportSend sendproc, void* closure ) +{ + CommsCtxt* result = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + result->isServer = isServer; + result->sendproc = sendproc; + result->sendClosure = closure; + result->util = util; + +#ifdef BEYOND_IR + /* default values; default is still IR where there's a choice */ + result->addr.conType = COMMS_CONN_IR; + result->addr.u.ip.ipAddr = 0L; /* force 'em to set it */ + result->addr.u.ip.port = 6000; + result->listenPort = 6001; +#endif + + return result; +} /* comms_make */ + +static void +cleanupInternal( CommsCtxt* comms ) +{ + MsgQueueElem* msg; + MsgQueueElem* next; + + for ( msg = comms->msgQueueHead; !!msg; msg = next ) { + next = msg->next; + XP_FREE( comms->mpool, msg->msg ); + XP_FREE( comms->mpool, msg ); + } + comms->queueLen = 0; + comms->msgQueueHead = comms->msgQueueTail = (MsgQueueElem*)NULL; +} /* cleanupInternal */ + +static void +cleanupAddrRecs( CommsCtxt* comms ) +{ + AddressRecord* recs; + AddressRecord* next; + + for ( recs = comms->recs; !!recs; recs = next ) { + next = recs->next; + XP_FREE( comms->mpool, recs ); + } + comms->recs = (AddressRecord*)NULL; +} /* cleanupAddrRecs */ + +void +comms_reset( CommsCtxt* comms, XP_Bool isServer ) +{ + cleanupInternal( comms ); + comms->isServer = isServer; + + cleanupAddrRecs( comms ); + + comms->nextChannelNo = 0; + + comms->connID = CONN_ID_NONE; +} /* comms_reset */ + +void +comms_destroy( CommsCtxt* comms ) +{ + cleanupInternal( comms ); + cleanupAddrRecs( comms ); + + XP_FREE( comms->mpool, comms ); +} /* comms_destroy */ + +void +comms_setConnID( CommsCtxt* comms, XP_U32 connID ) +{ + comms->connID = connID; + XP_STATUSF( "set connID to %ld", connID ); +} /* comms_setConnID */ + +CommsCtxt* +comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, + TransportSend sendproc, void* closure ) +{ + CommsCtxt* comms; + XP_Bool isServer; + XP_U16 nAddrRecs; + AddressRecord** prevsAddrNext; + MsgQueueElem** prevsQueueNext; + short i; + + isServer = stream_getU8( stream ); + comms = comms_make( MPPARM(mpool) util, isServer, sendproc, closure ); + + comms->connID = stream_getU32( stream ); + + comms->nextChannelNo = stream_getU16( stream ); +#ifdef DEBUG + comms->nUniqueBytes = stream_getU16( stream ); +#endif + comms->queueLen = stream_getU8( stream ); + + nAddrRecs = stream_getU8( stream ); + prevsAddrNext = &comms->recs; + for ( i = 0; i < nAddrRecs; ++i ) { + AddressRecord* rec = (AddressRecord*)XP_MALLOC( mpool, sizeof(*rec)); + XP_MEMSET( rec, 0, sizeof(*rec) ); + stream_getBytes( stream, &rec->addr, sizeof(rec->addr) ); + + rec->nextMsgID = stream_getU16( stream ); + rec->lastMsgReceived = stream_getU16( stream ); + rec->channelNo = stream_getU16( stream ); + +#ifdef DEBUG + rec->lastACK = stream_getU16( stream ); + rec->nUniqueBytes = stream_getU16( stream ); +#endif + + rec->next = (AddressRecord*)NULL; + *prevsAddrNext = rec; + prevsAddrNext = &rec->next; + } + + prevsQueueNext = &comms->msgQueueHead; + for ( i = 0; i < comms->queueLen; ++i ) { + MsgQueueElem* msg = (MsgQueueElem*)XP_MALLOC( mpool, sizeof(*msg) ); + + msg->channelNo = stream_getU16( stream ); + msg->msgID = stream_getU32( stream ); + + msg->len = stream_getU16( stream ); + msg->msg = (XP_U8*)XP_MALLOC( mpool, msg->len ); + stream_getBytes( stream, msg->msg, msg->len ); + + msg->next = (MsgQueueElem*)NULL; + *prevsQueueNext = comms->msgQueueTail = msg; + comms->msgQueueTail = msg; + prevsQueueNext = &msg->next; + } + +#ifdef BEYOND_IR + comms->addr.conType = stream_getBits( stream, 3 ); + comms->addr.u.ip.ipAddr = stream_getU32( stream ); + comms->addr.u.ip.port = stream_getU16( stream ); + comms->listenPort = stream_getU16( stream ); + /* tell client about the port */ + util_listenPortChange( util, comms->listenPort ); +#endif + + XP_ASSERT( stream_getU32( stream ) == cEND ); + + return comms; +} /* comms_makeFromStream */ + +void +comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream ) +{ + XP_U16 nAddrRecs; + AddressRecord* rec; + MsgQueueElem* msg; + + stream_putU8( stream, (XP_U8)comms->isServer ); + stream_putU32( stream, comms->connID ); + stream_putU16( stream, comms->nextChannelNo ); +#ifdef DEBUG + stream_putU16( stream, comms->nUniqueBytes ); +#endif + + XP_ASSERT( comms->queueLen <= 255 ); + stream_putU8( stream, (XP_U8)comms->queueLen ); + + nAddrRecs = countAddrRecs(comms); + stream_putU8( stream, (XP_U8)nAddrRecs ); + + for ( rec = comms->recs; !!rec; rec = rec->next ) { + stream_putBytes( stream, &rec->addr, sizeof(rec->addr) ); + stream_putU16( stream, (XP_U16)rec->nextMsgID ); + stream_putU16( stream, (XP_U16)rec->lastMsgReceived ); + stream_putU16( stream, rec->channelNo ); +#ifdef DEBUG + stream_putU16( stream, rec->lastACK ); + stream_putU16( stream, rec->nUniqueBytes ); +#endif + } + + for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { + stream_putU16( stream, msg->channelNo ); + stream_putU32( stream, msg->msgID ); + + stream_putU16( stream, msg->len ); + stream_putBytes( stream, msg->msg, msg->len ); + } + +#ifdef BEYOND_IR + stream_putBits( stream, 3, comms->addr.conType ); + stream_putU32( stream, comms->addr.u.ip.ipAddr ); + stream_putU16( stream, comms->addr.u.ip.port ); + stream_putU16( stream, comms->listenPort ); +#endif + +#ifdef DEBUG + stream_putU32( stream, cEND ); +#endif +} /* comms_writeToStream */ + +void +comms_getAddr( CommsCtxt* comms, CommsAddrRec* addr, XP_U16* listenPort ) +{ + XP_MEMCPY( addr, &comms->addr, sizeof(*addr) ); + *listenPort = comms->listenPort; +} /* comms_getAddr */ + +void +comms_setAddr( CommsCtxt* comms, CommsAddrRec* addr, XP_U16 listenPort ) +{ + XP_MEMCPY( &comms->addr, addr, sizeof(comms->addr) ); + comms->listenPort = listenPort; +#ifdef BEYOND_IR + util_listenPortChange( comms->util, listenPort ); +#endif +} /* comms_setAddr */ + +CommsConnType +comms_getConType( CommsCtxt* comms ) +{ + return comms->addr.conType; +} /* comms_getConType */ + +/* Send a message using the sequentially next MsgID. Save the message so + * resend can work. */ +XP_S16 +comms_send( CommsCtxt* comms, CommsConnType conType, XWStreamCtxt* stream ) +{ + XP_U16 streamSize = stream_getSize( stream ); + XP_U16 headerLen; + XP_PlayerAddr channelNo = stream_getAddress( stream ); + AddressRecord* rec = getRecordFor( comms, channelNo ); + MsgID msgID = (!!rec)? ++rec->nextMsgID : 0; + MsgID lastMsgRcd = (!!rec)? rec->lastMsgReceived : 0; + MsgQueueElem* newMsgElem; + XWStreamCtxt* msgStream; + + XP_DEBUGF( "assigning msgID of %ld on chnl %d", msgID, channelNo ); + +#ifdef DEBUG + if ( !!rec ) { + rec->nUniqueBytes += streamSize; + } else { + comms->nUniqueBytes += streamSize; + } +#endif + + newMsgElem = (MsgQueueElem*)XP_MALLOC( comms->mpool, + sizeof( *newMsgElem ) ); + newMsgElem->channelNo = channelNo; + newMsgElem->msgID = msgID; + + msgStream = mem_stream_make( MPPARM(comms->mpool) + util_getVTManager(comms->util), + NULL, 0, + (MemStreamCloseCallback)NULL ); + stream_open( msgStream ); + stream_putU32( msgStream, comms->connID ); + +#ifdef BEYOND_IR + if ( conType == COMMS_CONN_IP ) { + stream_putU16( msgStream, comms->listenPort ); + XP_LOGF( "wrote return port to stream: %d", comms->listenPort ); + } +#endif + + stream_putU16( msgStream, channelNo ); + stream_putU32( msgStream, msgID ); + stream_putU32( msgStream, lastMsgRcd ); + + headerLen = stream_getSize( msgStream ); + newMsgElem->len = streamSize + headerLen; + newMsgElem->msg = (XP_U8*)XP_MALLOC( comms->mpool, newMsgElem->len ); + + stream_getBytes( msgStream, newMsgElem->msg, headerLen ); + stream_destroy( msgStream ); + + stream_getBytes( stream, newMsgElem->msg + headerLen, streamSize ); + + addToQueue( comms, newMsgElem ); + + return sendMsg( comms, newMsgElem ); +} /* comms_send */ + +/* Add new message to the end of the list. The list needs to be kept in order + * by ascending msgIDs within each channel since if there's a resend that's + * the order in which they need to be sent. + */ +static void +addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem ) +{ + newMsgElem->next = (MsgQueueElem*)NULL; + if ( !comms->msgQueueHead ) { + comms->msgQueueHead = comms->msgQueueTail = newMsgElem; + XP_ASSERT( comms->queueLen == 0 ); + comms->queueLen = 1; + } else { + XP_ASSERT( !!comms->msgQueueTail ); + comms->msgQueueTail->next = newMsgElem; + comms->msgQueueTail = newMsgElem; + + XP_ASSERT( comms->queueLen > 0 ); + ++comms->queueLen; + } + XP_STATUSF( "addToQueue: queueLen now %d", comms->queueLen ); +} /* addToQueue */ + +#ifdef DEBUG +static void +printQueue( CommsCtxt* comms ) +{ + MsgQueueElem* elem; + short i; + + for ( elem = comms->msgQueueHead, i = 0; i < comms->queueLen; + elem = elem->next, ++i ) { + XP_STATUSF( "\t%d: channel: %d; msgID: %ld", + i+1, elem->channelNo, elem->msgID ); + } +} +#else +#define printQueue(foo) +#endif +/* We've received on some channel a message with a certain ID. This means + * that all messages sent on that channel with lower IDs have been received + * and can be removed from our queue. + */ +static void +removeFromQueue( CommsCtxt* comms, XP_PlayerAddr channelNo, MsgID msgID ) +{ + MsgQueueElem* elem; + MsgQueueElem* prev; + + XP_STATUSF( "looking to remove msgs prior or equal to %ld for channel %d " + "(queue len now %d)", + msgID, channelNo, comms->queueLen ); + + for ( prev = (MsgQueueElem*)NULL, elem = comms->msgQueueHead; + !!elem; prev = elem, elem = elem->next ) { + + /* remove the 0-channel message if we've established a channel number. + Only clients should have any 0-channel messages in the queue, and + receiving something from the server is an implicit ACK */ + + if ( elem->channelNo == 0 && channelNo != 0 ) { + XP_ASSERT( !comms->isServer ); /* I've seen this fail once */ + XP_ASSERT( elem->msgID == 0 ); /* will the check below pass? */ + } else if ( elem->channelNo != channelNo ) { + continue; + } + + if ( elem->msgID <= msgID ) { + + if ( !prev ) { /* it's the first element */ + comms->msgQueueHead = elem->next; + prev = comms->msgQueueHead; /* so elem=prev below will work */ + } else { + prev->next = elem->next; + } + + if ( comms->msgQueueTail == elem ) { + comms->msgQueueTail = prev; + } + + XP_FREE( comms->mpool, elem->msg ); + XP_FREE( comms->mpool, elem ); + elem = prev; + --comms->queueLen; + + if ( !elem ) { + break; + } + } + } + XP_STATUSF( "removeFromQueue: queueLen now %d", comms->queueLen ); + + XP_ASSERT( comms->queueLen > 0 || comms->msgQueueHead == NULL ); + + printQueue( comms ); +} /* removeFromQueue */ + +static XP_S16 +sendMsg( CommsCtxt* comms, MsgQueueElem* elem ) +{ + XP_S16 result; + XP_PlayerAddr channelNo; + CommsAddrRec* addr; + + channelNo = elem->channelNo; + + if ( !channelToAddress( comms, channelNo, &addr ) ) { + addr = NULL; + } + + XP_ASSERT( !!comms->sendproc ); + result = (*comms->sendproc)( elem->msg, elem->len, addr, + comms->sendClosure ); + return result; +} /* sendMsg */ + +XP_S16 +comms_resendAll( CommsCtxt* comms ) +{ + MsgQueueElem* msg; + XP_S16 result = 0; + + for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { + XP_S16 oneResult = sendMsg( comms, msg ); + if ( result == 0 && oneResult != 0 ) { + result = oneResult; + } + XP_STATUSF( "resend: msgid=%ld; rslt=%d", msg->msgID, oneResult ); + } + + return result; +} /* comms_resend */ + +/* read a raw buffer into a stream, stripping off the headers and keeping + * any necessary stats. + * + * Keep track of return addresses by channel. If the message's channel number + * is 0, assign a new channel number and associate an address with it. + * Otherwise update the address, which may have changed since we last heard + * from this channel. + * + * There may be messages that are only about the comms 'protocol', that + * contain nothing to be passed to the server. In that case, return false + * indicating the caller that all processing is finished. + * + * conType tells both how to interpret the addr and whether to expect any + * special fields in the message itself. In the IP case, for example, the + * port component of a return address is in the message but the IP address + * component will be passed in. + */ +XP_Bool +comms_checkIncommingStream( CommsCtxt* comms, XWStreamCtxt* stream, + CommsAddrRec* addr ) +{ + XP_U16 channelNo; + XP_U32 connID; + MsgID msgID; + MsgID lastMsgRcd; + XP_Bool validMessage = XP_TRUE; + AddressRecord* recs = (AddressRecord*)NULL; + + connID = stream_getU32( stream ); + XP_STATUSF( "read connID of %ld", connID ); + +#ifdef BEYOND_IR + if ( addr->conType == COMMS_CONN_IP ) { + addr->u.ip.port = stream_getU16( stream ); + XP_LOGF( "read return port from stream: %d", addr->u.ip.port ); + } +#endif + + if ( comms->connID == connID || comms->connID == CONN_ID_NONE ) { + + channelNo = stream_getU16( stream ); + msgID = stream_getU32( stream ); + lastMsgRcd = stream_getU32( stream ); + + XP_DEBUGF( "rcd: msg %ld on chnl %d", msgID, channelNo ); + + removeFromQueue( comms, channelNo, lastMsgRcd ); + + if ( channelNo == 0 ) { + XP_ASSERT( comms->isServer ); + XP_ASSERT( msgID == 0 ); + channelNo = ++comms->nextChannelNo; + XP_STATUSF( "assigning channelNo=%d", channelNo ); + } else { + recs = getRecordFor( comms, channelNo ); + /* messageID for an incomming message should be one greater than + * the id most recently used for that channel. */ + if ( !!recs && (msgID != recs->lastMsgReceived + 1) ) { + XP_DEBUGF( "unex msgID %ld (expt %ld)", + msgID, recs->lastMsgReceived+1 ); + validMessage = XP_FALSE; + } +#ifdef DEBUG + if ( !!recs ) { + XP_ASSERT( lastMsgRcd < 0x0000FFFF ); + recs->lastACK = (XP_U16)lastMsgRcd; + } +#endif + } + + if ( validMessage ) { + recs = rememberChannelAddress( comms, channelNo, addr ); + + stream_setAddress( stream, channelNo ); + + if ( !!recs ) { + recs->lastMsgReceived = msgID; + } + XP_STATUSF( "set channel %d's lastMsgReceived to %ld", + channelNo, msgID ); + } + } else { + XP_STATUSF( "refusing non-matching connID; got %ld, wanted %ld", + connID, comms->connID ); + } + return validMessage; +} /* comms_checkIncommingStream */ + +#ifdef DEBUG +void +comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream ) +{ + XP_UCHAR buf[100]; + AddressRecord* rec; + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), (XP_UCHAR*)"msg queue len: %d\n", + comms->queueLen ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN( buf ) ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"channel-less bytes sent: %d\n", + comms->nUniqueBytes ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN( buf ) ); + + for ( rec = comms->recs; !!rec; rec = rec->next ) { + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)" Stats for channel: %d\n", + rec->channelNo ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN( buf ) ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"Last msg sent: %ld\n", + rec->nextMsgID ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN( buf ) ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"Unique bytes sent: %d\n", + rec->nUniqueBytes ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN( buf ) ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"Last message acknowledged: %d\n", + rec->lastACK); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN( buf ) ); + } +} /* comms_getStats */ +#endif + +static AddressRecord* +rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo, + CommsAddrRec* addr ) +{ + AddressRecord* recs = NULL; + if ( addr != NULL ) { + recs = getRecordFor( comms, channelNo ); + if ( !!recs ) { + /* Looks as if this will overwrite the address each time a new + message comes in. I *guess* that's right... */ + XP_MEMCPY( &recs->addr, addr, sizeof(recs->addr) ); + } else { + /* not found; add a new entry */ + recs = (AddressRecord*)XP_MALLOC( comms->mpool, sizeof(*recs) ); + + recs->nextMsgID = 0; + recs->channelNo = channelNo; + XP_MEMCPY( &recs->addr, addr, sizeof(recs->addr) ); +#ifdef DEBUG + recs->nUniqueBytes = 0; +#endif + recs->next = comms->recs; + comms->recs = recs; + } + } + return recs; +} /* rememberChannelAddress */ + +static XP_Bool +channelToAddress( CommsCtxt* comms, XP_PlayerAddr channelNo, + CommsAddrRec** addr ) +{ + AddressRecord* recs = getRecordFor( comms, channelNo ); + + if ( !!recs ) { + *addr = &recs->addr; + return XP_TRUE; + } else { + return XP_FALSE; + } +} /* channelToAddress */ + +static AddressRecord* +getRecordFor( CommsCtxt* comms, XP_PlayerAddr channelNo ) +{ + AddressRecord* recs; + + if ( channelNo == CHANNEL_NONE ) { + return (AddressRecord*)NULL; + } + + for ( recs = comms->recs; !!recs; recs = recs->next ) { + if ( recs->channelNo == channelNo ) { + return recs; + } + } + return (AddressRecord*)NULL; +} /* getRecordFor */ + +static XP_U16 +countAddrRecs( CommsCtxt* comms ) +{ + short count = 0; + AddressRecord* recs; + for ( recs = comms->recs; !!recs; recs = recs->next ) { + ++count; + } + return count; +} /* countAddrRecs */ + +#endif /* #ifndef XWFEATURE_STANDALONE_ONLY */ diff --git a/xwords4/common/comms.h b/xwords4/common/comms.h new file mode 100644 index 000000000..cc3b3c302 --- /dev/null +++ b/xwords4/common/comms.h @@ -0,0 +1,93 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _COMMS_H_ +#define _COMMS_H_ + +#include "comtypes.h" +#include "mempool.h" + +#define CHANNEL_NONE ((XP_PlayerAddr)0) +#define CONN_ID_NONE 0L + +typedef XP_U32 MsgID; + +typedef enum { + COMMS_CONN_UNUSED, /* I want errors on uninited case */ + COMMS_CONN_IR, + COMMS_CONN_IP, + + LAST_____FOO +} CommsConnType; + + +typedef struct CommsAddrRec { + CommsConnType conType; + + union { + struct { + + XP_U32 ipAddr; + XP_U16 port; /* return port, not sent-from */ + } ip; + struct { + /* nothing? */ + XP_UCHAR foo; /* wince doesn't like nothing here */ + } ir; + } u; +} CommsAddrRec; + +typedef XP_S16 (*TransportSend)( XP_U8* buf, XP_U16 len, + CommsAddrRec* addr, + void* closure ); + +CommsCtxt* comms_make( MPFORMAL XW_UtilCtxt* util, + XP_Bool isServer, TransportSend sendproc, + void* closure ); + +void comms_reset( CommsCtxt* comms, XP_Bool isServer ); +void comms_destroy( CommsCtxt* comms ); + +void comms_setConnID( CommsCtxt* comms, XP_U32 connID ); + +void comms_getAddr( CommsCtxt* comms, CommsAddrRec* addr, XP_U16* listenPort ); +void comms_setAddr( CommsCtxt* comms, CommsAddrRec* addr, XP_U16 listenPort ); +CommsConnType comms_getConType( CommsCtxt* comms ); + +CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, + XW_UtilCtxt* util, TransportSend sendproc, + void* closure ); +void comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream ); + +/* void comms_setDefaultTarget( CommsCtxt* comms, char* hostName, */ +/* short hostPort ); */ + +XP_S16 comms_send( CommsCtxt* comms, CommsConnType conType, + XWStreamCtxt* stream ); +XP_S16 comms_resendAll( CommsCtxt* comms ); + + +XP_Bool comms_checkIncommingStream( CommsCtxt* comms, XWStreamCtxt* stream, + CommsAddrRec* addr ); + +# ifdef DEBUG +void comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream ); +# endif + +#endif /* _COMMS_H_ */ diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h new file mode 100644 index 000000000..8da0701c1 --- /dev/null +++ b/xwords4/common/comtypes.h @@ -0,0 +1,111 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + + +#ifndef _COMTYPES_H_ +#define _COMTYPES_H_ + +#include "xptypes.h" + +typedef struct XP_Rect { + XP_S16 left; + XP_S16 top; + XP_S16 width; + XP_S16 height; +} XP_Rect; + +typedef XP_U16 CellTile; + +typedef XP_U8 Tile; + +typedef void* XP_Bitmap; + +/* I'm going to try putting all forward "class" decls in the same file */ +typedef struct BoardCtxt BoardCtxt; +typedef struct CommMgrCtxt CommMgrCtxt; +typedef struct DictionaryCtxt DictionaryCtxt; +typedef struct DrawCtx DrawCtx; +typedef struct EngineCtxt EngineCtxt; +typedef struct ModelCtxt ModelCtxt; +typedef struct CommsCtxt CommsCtxt; +typedef struct PlayerSocket PlayerSocket; +typedef struct ScoreBdContext ScoreBdContext; +typedef struct ServerCtxt ServerCtxt; +typedef struct XWStreamCtxt XWStreamCtxt; +typedef struct TrayContext TrayContext; +typedef struct PoolContext PoolContext; +typedef struct XW_UtilCtxt XW_UtilCtxt; + +typedef XP_U16 XP_PlayerAddr; + +typedef enum { + TIMER_PENDOWN = 1, /* ARM doesn't like ids of 0... */ + TIMER_TIMERTICK +} XWTimerReason; + +#define MAX_NUM_PLAYERS 4 +#define PLAYERNUM_NBITS 2 +#define NPLAYERS_NBITS 3 +#define EMPTIED_TRAY_BONUS 50 + +/* I need a way to communiate prefs to common/ code. For now, though, I'll + * leave storage of these values up to the platforms. First, because I don't + * want to deal with versioning in the common code. Second, becuase they + * already have the notion of per-game and all-game prefs. + */ +typedef struct CommonPrefs { + XP_Bool showBoardArrow; /* applies to all games */ + XP_Bool showRobotScores; /* applies to all games */ +} CommonPrefs; + +/* used for all vtables */ +#define SET_VTABLE_ENTRY( vt, name, prefix ) \ + (vt)->m_##name = prefix##_##name + +#ifdef DEBUG +# define DEBUG_ASSIGN(a,b) (a) = (b) +#else +# define DEBUG_ASSIGN(a,b) +#endif + +#ifdef MEM_DEBUG +# define XP_MALLOC(pool,nbytes) \ + mpool_alloc((pool),(nbytes),__FILE__,__LINE__) +# define XP_REALLOC(pool,p,s) mpool_realloc((pool),(p),(s)) +# define XP_FREE(pool,p) mpool_free((pool), (p), __FILE__, __LINE__) + +# define MPFORMAL_NOCOMMA MemPoolCtx* mpool +# define MPFORMAL MPFORMAL_NOCOMMA, +# define MPSLOT MPFORMAL_NOCOMMA; +# define MPPARM_NOCOMMA(p) (p) +# define MPPARM(p) MPPARM_NOCOMMA(p), +# define MPASSIGN(slot,val) (slot)=(val) + +#else + +# define MPFORMAL_NOCOMMA +# define MPFORMAL +# define MPSLOT +# define MPPARM_NOCOMMA(p) +# define MPPARM(p) +# define MPASSIGN(slot,val) + +#endif + +#endif diff --git a/xwords4/common/config.mk b/xwords4/common/config.mk new file mode 100644 index 000000000..980f742b6 --- /dev/null +++ b/xwords4/common/config.mk @@ -0,0 +1,70 @@ +# -*- mode: makefile -*- + +# Copyright 2002 by Eric House +# +# 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. + +INCLUDES += -I../common -I./ + +COMMONDIR=../common +COMMONOBJDIR=../common/$(PLATFORM) + +COMMONSRC = \ + $(COMMONDIR)/board.c \ + $(COMMONDIR)/tray.c \ + $(COMMONDIR)/draw.c \ + $(COMMONDIR)/model.c \ + $(COMMONDIR)/mscore.c \ + $(COMMONDIR)/server.c \ + $(COMMONDIR)/pool.c \ + $(COMMONDIR)/game.c \ + $(COMMONDIR)/dictnry.c \ + $(COMMONDIR)/engine.c \ + $(COMMONDIR)/memstream.c \ + $(COMMONDIR)/comms.c \ + $(COMMONDIR)/mempool.c \ + $(COMMONDIR)/movestak.c \ + $(COMMONDIR)/strutils.c \ + $(COMMONDIR)/vtabmgr.c \ + +# PENDING: define this in terms of above!!! + +COMMON1 = \ + $(COMMONOBJDIR)/board.o \ + $(COMMONOBJDIR)/tray.o \ + $(COMMONOBJDIR)/draw.o \ + $(COMMONOBJDIR)/model.o \ + +COMMON2 = \ + $(COMMONOBJDIR)/mscore.o \ + $(COMMONOBJDIR)/server.o \ + $(COMMONOBJDIR)/pool.o \ + +COMMON3 = \ + $(COMMONOBJDIR)/game.o \ + $(COMMONOBJDIR)/dictnry.o \ + $(COMMONOBJDIR)/engine.o \ + +COMMON4 = \ + $(COMMONOBJDIR)/memstream.o \ + $(COMMONOBJDIR)/comms.o \ + $(COMMONOBJDIR)/mempool.o \ + +COMMON5 = \ + $(COMMONOBJDIR)/movestak.o \ + $(COMMONOBJDIR)/strutils.o \ + $(COMMONOBJDIR)/vtabmgr.o \ + +COMMONOBJ = $(COMMON1) $(COMMON2) $(COMMON3) $(COMMON4) $(COMMON5) diff --git a/xwords4/common/dawg.h b/xwords4/common/dawg.h new file mode 100644 index 000000000..d17b99273 --- /dev/null +++ b/xwords4/common/dawg.h @@ -0,0 +1,75 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1998-2001 by Eric House (fixin@peak.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. + */ + +#ifndef _DAWG_H_ +#define _DAWG_H_ + +#include "xptypes.h" + +/* + * the bits field has five bits for the character (0-based rather than + * 'a'-based, of course; one bit each indicating whether the edge may + * be terminal and whether it's the last edge of a sub-array; and a final + * bit that's overflow from the highByte field allowing indices to be in + * the range 0-(2^^17)-1 + */ +#define LETTERMASK 0x1f +#define ACCEPTINGMASK 0x20 +#define LASTEDGEMASK 0x40 +#define LASTBITMASK 0x80 + +typedef struct array_edge_old { + XP_U8 highByte; + XP_U8 lowByte; + XP_U8 bits; +} array_edge_old; + +typedef struct array_edge_new { + array_edge_old o; + XP_U8 moreBits; +} array_edge_new; + +typedef XP_U8 array_edge; + +/* This thing exists only in current xwords dicts (on PalmOS); not sure if I + * should do away with it. + */ +typedef struct dawg_header { + unsigned long numWords; + unsigned char firstEdgeRecNum; + unsigned char charTableRecNum; + unsigned char valTableRecNum; + unsigned char reserved[3]; /* worst case this points to a new resource */ +} dawg_header; + +/* Part of xwords3 dictionaries on PalmOS */ +typedef struct Xloc_header { + unsigned char langCodeFlags; /* can't do bitfields; gcc for pilot and x86 + seem to generate different code */ + unsigned char padding; /* ptrs to the shorts in Xloc_specialEntry + will otherwise be odd */ +} Xloc_header; + +typedef struct Xloc_specialEntry { + unsigned char textVersion[4]; /* string can be up to 3 chars long */ + short hasLarge; + short hasSmall; +} Xloc_specialEntry; + +#endif /* _DAWG_H_ */ diff --git a/xwords4/common/dictnry.c b/xwords4/common/dictnry.c new file mode 100644 index 000000000..91a13af4e --- /dev/null +++ b/xwords4/common/dictnry.c @@ -0,0 +1,482 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2000 by Eric House (fixin@peak.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. + */ + +#ifdef USE_STDIO +# include +# include +#endif + +#include "comtypes.h" +#include "dictnryp.h" +#include "xwstream.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +/***************************************************************************** + * + ****************************************************************************/ +void +setBlankTile( DictionaryCtxt* dctx ) +{ + XP_U16 i; + + dctx->blankTile = -1; /* no known blank */ + + for ( i = 0; i < dctx->nFaces; ++i ) { + if ( dctx->faces16[i] == 0 ) { + dctx->blankTile = (XP_U8)i; + XP_LOGF( "blank tile index: %d", i ); + break; + } + } +} /* setBlankTile */ + +XP_Bool +dict_hasBlankTile( DictionaryCtxt* dict ) +{ + return dict->blankTile >= 0; +} /* dict_hasBlankTile */ + +Tile +dict_getBlankTile( DictionaryCtxt* dict ) +{ + XP_ASSERT( dict_hasBlankTile(dict) ); + return (Tile)dict->blankTile; +} /* dict_getBlankTile */ + +XP_U16 +dict_getTileValue( DictionaryCtxt* dict, Tile tile ) +{ + tile &= TILE_VALUE_MASK; + XP_ASSERT( tile < dict->nFaces ); + tile *= 2; + return dict->countsAndValues[tile+1]; +} /* dict_getTileValue */ + +static XP_UCHAR +dict_getTileChar( DictionaryCtxt* dict, Tile tile ) +{ + XP_ASSERT( tile < dict->nFaces ); + XP_ASSERT( (dict->faces16[tile] & 0xFF00) == 0 ); /* no unicode yet */ + return (XP_UCHAR)dict->faces16[tile]; +} /* dict_getTileValue */ + +XP_U16 +dict_numTiles( DictionaryCtxt* dict, Tile tile ) +{ + tile *= 2; + return dict->countsAndValues[tile]; +} /* dict_numTiles */ + +XP_U16 +dict_numTileFaces( DictionaryCtxt* dict ) +{ + return dict->nFaces; +} /* dict_numTileFaces */ + +XP_U16 +dict_tilesToString( DictionaryCtxt* ctxt, Tile* tiles, XP_U16 nTiles, + XP_UCHAR* buf ) +{ + XP_UCHAR* bufIn = buf; + + while ( nTiles-- ) { + Tile tile = *tiles++; + XP_UCHAR face = dict_getTileChar(ctxt, tile); + + if ( IS_SPECIAL(face) ) { + XP_UCHAR* chars = ctxt->chars[(XP_U16)face]; + XP_U16 len = XP_STRLEN( chars ); + XP_MEMCPY( buf, chars, len ); + buf += len; + } else { + XP_ASSERT ( tile != ctxt->blankTile ); /* printing blank should be + handled by specials + mechanism */ + *buf++ = face; + } + } + + *buf = '\0'; + return buf - bufIn; +} /* dict_tilesToString */ + +Tile +dict_tileForString( DictionaryCtxt* dict, XP_UCHAR* key ) +{ + XP_U16 nFaces = dict_numTileFaces( dict ); + Tile tile; + XP_Bool keyNotSpecial = XP_STRLEN((char*)key) == 1; + + for ( tile = 0; tile < nFaces; ++tile ) { + XP_UCHAR face = dict_getTileChar( dict, tile ); + if ( IS_SPECIAL(face) ) { + XP_UCHAR* chars = dict->chars[(XP_U16)face]; + if ( 0 == XP_STRNCMP( chars, key, XP_STRLEN(chars) ) ) { + return tile; + } + } else if ( keyNotSpecial && (face == *key) ) { + return tile; + } + } + return EMPTY_TILE; +} /* dict_tileForChar */ + +XP_Bool +dict_tilesAreSame( DictionaryCtxt* dict1, DictionaryCtxt* dict2 ) +{ + XP_Bool result = XP_FALSE; + + Tile i; + XP_U16 nTileFaces = dict_numTileFaces( dict1 ); + + if ( nTileFaces == dict_numTileFaces( dict2 ) ) { + for ( i = 0; i < nTileFaces; ++i ) { + + XP_UCHAR face1, face2; + + if ( dict_getTileValue( dict1, i ) + != dict_getTileValue( dict2, i ) ){ + break; + } +#if 1 + face1 = dict_getTileChar( dict1, i ); + face2 = dict_getTileChar( dict2, i ); + if ( face1 != face2 ) { + break; + } +#else + face1 = dict_getTileChar( dict1, i ); + face2 = dict_getTileChar( dict2, i ); + if ( IS_SPECIAL(face1) != IS_SPECIAL(face2) ) { + break; + } + if ( IS_SPECIAL(face1) ) { + XP_UCHAR* chars1 = dict1->chars[face1]; + XP_UCHAR* chars2 = dict2->chars[face2]; + XP_U16 len = XP_STRLEN(chars1); + if ( 0 != XP_STRNCMP( chars1, chars2, len ) ) { + break; + } + } else if ( face1 != face2 ) { + break; + } +#endif + if ( dict_numTiles( dict1, i ) != dict_numTiles( dict2, i ) ) { + break; + } + } + result = i == nTileFaces; /* did we get that far */ + } + return result; +} /* dict_tilesAreSame */ + +void +dict_writeToStream( DictionaryCtxt* dict, XWStreamCtxt* stream ) +{ + XP_U16 maxCount = 0; + XP_U16 maxValue = 0; + XP_U16 i, nSpecials; + XP_U16 maxCountBits, maxValueBits; + + stream_putBits( stream, 6, dict->nFaces ); + + for ( i = 0; i < dict->nFaces*2; i+=2 ) { + XP_U16 count, value; + + count = dict->countsAndValues[i]; + if ( maxCount < count ) { + maxCount = count; + } + + value = dict->countsAndValues[i+1]; + if ( maxValue < value ) { + maxValue = value; + } + } + + maxCountBits = bitsForMax( maxCount ); + maxValueBits = bitsForMax( maxValue ); + + stream_putBits( stream, 3, maxCountBits ); /* won't be bigger than 5 */ + stream_putBits( stream, 3, maxValueBits ); + + for ( i = 0; i < dict->nFaces*2; i+=2 ) { + stream_putBits( stream, maxCountBits, dict->countsAndValues[i] ); + stream_putBits( stream, maxValueBits, dict->countsAndValues[i+1] ); + } + + for ( i = 0; i < dict->nFaces; ++i ) { + stream_putU8( stream, (XP_U8)dict->faces16[i] ); + } + + for ( nSpecials = i = 0; i < dict->nFaces; ++i ) { + XP_UCHAR face = dict_getTileChar( dict, (Tile)i ); + if ( IS_SPECIAL( face ) ) { + stringToStream( stream, dict->chars[nSpecials++] ); + } + } +} /* dict_writeToStream */ + +static void +freeSpecials( DictionaryCtxt* dict ) +{ + Tile t; + XP_U16 nSpecials; + + for ( nSpecials = t = 0; t < dict->nFaces; ++t ) { + XP_UCHAR face = dict_getTileChar( dict, t ); + if ( IS_SPECIAL( face ) ) { + + XP_ASSERT( !!dict->chars[nSpecials] ); + XP_FREE( dict->mpool, dict->chars[nSpecials] ); + + if ( !!dict->bitmaps[nSpecials].largeBM ) { + XP_FREE( dict->mpool, dict->bitmaps[nSpecials].largeBM ); + } + if ( !!dict->bitmaps[nSpecials].smallBM ) { + XP_FREE( dict->mpool, dict->bitmaps[nSpecials].smallBM ); + } + + ++nSpecials; + } + } + if ( nSpecials > 0 ) { + XP_FREE( dict->mpool, dict->chars ); + XP_FREE( dict->mpool, dict->bitmaps ); + } +} /* freeSpecials */ + +static void +common_destructor( DictionaryCtxt* dict ) +{ + freeSpecials( dict ); + + XP_FREE( dict->mpool, dict->countsAndValues ); + XP_FREE( dict->mpool, dict->faces16 ); + + XP_FREE( dict->mpool, dict ); +} /* dict */ + +void +dict_loadFromStream( DictionaryCtxt* dict, XWStreamCtxt* stream ) +{ + XP_U8 nFaces; + XP_U16 maxCountBits, maxValueBits; + XP_U16 i, nSpecials; + XP_UCHAR* localTexts[32]; + + XP_ASSERT( !dict->destructor ); + dict->destructor = common_destructor; + + nFaces = (XP_U8)stream_getBits( stream, 6 ); + maxCountBits = (XP_U16)stream_getBits( stream, 3 ); + maxValueBits = (XP_U16)stream_getBits( stream, 3 ); + + dict->nFaces = nFaces; + + dict->countsAndValues = + (XP_U8*)XP_MALLOC( dict->mpool, + sizeof(dict->countsAndValues[0]) * nFaces * 2 ); + + for ( i = 0; i < dict->nFaces*2; i+=2 ) { + dict->countsAndValues[i] = (XP_U8)stream_getBits( stream, + maxCountBits ); + dict->countsAndValues[i+1] = (XP_U8)stream_getBits( stream, + maxValueBits ); + } + + dict->faces16 = (XP_CHAR16*)XP_MALLOC( dict->mpool, + sizeof(dict->faces16[0]) * nFaces ); + for ( i = 0; i < dict->nFaces; ++i ) { + dict->faces16[i] = (XP_CHAR16)stream_getU8( stream ); + } + + for ( nSpecials = i = 0; i < nFaces; ++i ) { + XP_UCHAR face = dict_getTileChar( dict, (Tile)i ); + if ( IS_SPECIAL( face ) ) { + XP_UCHAR* txt = stringFromStream( MPPARM(dict->mpool) stream ); + XP_ASSERT( !!txt ); + localTexts[nSpecials] = txt; + + ++nSpecials; + } + } + if ( nSpecials > 0 ) { + dict->bitmaps = + (SpecialBitmaps*)XP_MALLOC( dict->mpool, + nSpecials * sizeof(*dict->bitmaps) ); + XP_MEMSET( dict->bitmaps, 0, nSpecials * sizeof(*dict->bitmaps) ); + + dict->chars = (XP_UCHAR**)XP_MALLOC( dict->mpool, + nSpecials * sizeof(*dict->chars) ); + XP_MEMCPY(dict->chars, localTexts, nSpecials * sizeof(*dict->chars)); + } + + setBlankTile( dict ); +} /* dict_loadFromStream */ + +XP_UCHAR* +dict_getName( DictionaryCtxt* dict ) +{ + XP_ASSERT( !!dict ); + return dict->name; +} /* dict_getName */ + +XP_Bool +dict_faceIsBitmap( DictionaryCtxt* dict, Tile tile ) +{ + XP_UCHAR face = dict_getTileChar( dict, tile ); + return /* face != 0 && */IS_SPECIAL(face); +} /* dict_faceIsBitmap */ + +XP_Bitmap +dict_getFaceBitmap( DictionaryCtxt* dict, Tile tile, XP_Bool isLarge ) +{ + SpecialBitmaps* bitmaps; + XP_UCHAR face = dict_getTileChar( dict, tile ); + + XP_ASSERT( dict_faceIsBitmap( dict, tile ) ); + XP_ASSERT( !!dict->bitmaps ); + + bitmaps = &dict->bitmaps[(XP_U16)face]; + return isLarge? bitmaps->largeBM:bitmaps->smallBM; +} /* dict_getFaceBitmap */ + +#ifdef STUBBED_DICT + +#define BLANK_FACE '\0' + +static XP_U8 stub_english_data[] = { + /* count value face */ + 9, 1, 'A', + 2, 3, 'B', + 2, 3, 'C', + 4, 2, 'D', + 12, 1, 'E', + 2, 4, 'F', + 3, 2, 'G', + 2, 4, 'H', + 9, 1, 'I', + 1, 8, 'J', + 1, 5, 'K', + 4, 1, 'L', + 2, 3, 'M', + 6, 1, 'N', + 8, 1, 'O', + 2, 3, 'P', + 1, 10, 'Q', + 6, 1, 'R', + 4, 1, 'S', + 6, 1, 'T', + 4, 1, 'U', + 2, 4, 'V', + 2, 4, 'W', + 1, 8, 'X', + 2, 4, 'Y', + 1, 10, 'Z', + 2, 0, BLANK_FACE, /* BLANK1 */ +}; + +void +setStubbedSpecials( DictionaryCtxt* dict ) +{ + dict->chars = XP_MALLOC( dict->mpool, sizeof(char*) ); + dict->chars[0] = "_"; + +} /* setStubbedSpecials */ + +void +destroy_stubbed_dict( DictionaryCtxt* dict ) +{ + XP_FREE( dict->mpool, dict->countsAndValues ); + XP_FREE( dict->mpool, dict->faces16 ); + XP_FREE( dict->mpool, dict->chars ); + XP_FREE( dict->mpool, dict->name ); + XP_FREE( dict->mpool, dict->bitmaps ); + XP_FREE( dict->mpool, dict ); +} /* destroy_stubbed_dict */ + +DictionaryCtxt* +make_stubbed_dict( MPFORMAL_NOCOMMA ) +{ + DictionaryCtxt* dict = XP_MALLOC( mpool, sizeof(*dict) ); + XP_U8* data = stub_english_data; + XP_U16 datasize = sizeof(stub_english_data); + XP_U16 i; + + XP_MEMSET( dict, 0, sizeof(*dict) ); + + MPASSIGN( dict->mpool, mpool ); + dict->name = copyString( MPPARM(mpool) "Stub dictionary" ); + dict->nFaces = datasize/3; + + dict->destructor = destroy_stubbed_dict; + + dict->faces16 = XP_MALLOC( mpool, + dict->nFaces * sizeof(dict->faces16[0]) ); + for ( i = 0; i < datasize/3; ++i ) { + dict->faces16[i] = (XP_CHAR16)data[(i*3)+2]; + } + + dict->countsAndValues = XP_MALLOC( mpool, dict->nFaces*2 ); + for ( i = 0; i < datasize/3; ++i ) { + dict->countsAndValues[i*2] = data[(i*3)]; + dict->countsAndValues[(i*2)+1] = data[(i*3)+1]; + } + + dict->bitmaps = XP_MALLOC( mpool, sizeof(SpecialBitmaps) ); + dict->bitmaps->largeBM = dict->bitmaps->largeBM = NULL; + + setStubbedSpecials( dict ); + + setBlankTile( dict ); + + return dict; +} /* make_subbed_dict */ + +#endif /* STUBBED_DICT */ + +#ifndef OVERRIDE_EDGE_FOR_INDEX +array_edge* +dict_edge_for_index( DictionaryCtxt* dict, XP_U32 index ) +{ + array_edge* result; + + if ( index == 0 ) { + result = NULL; + } else { + XP_ASSERT( index < dict->numEdges ); +#ifdef NODE_CAN_4 + index *= dict->nodeSize; +#else + index *= 3; +#endif + result = &dict->base[index]; + } + return result; +} /* dict_edge_for_index */ +#endif + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/dictnry.h b/xwords4/common/dictnry.h new file mode 100644 index 000000000..6857898e3 --- /dev/null +++ b/xwords4/common/dictnry.h @@ -0,0 +1,154 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef __DICTNRY_H__ +#define __DICTNRY_H__ + +#include "comtypes.h" + +#include "dawg.h" +#include "model.h" +#include "mempool.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define LETTER_NONE '\0' +#define IS_SPECIAL(face) (((XP_CHAR16)(face)) < 0x0020) + +typedef XP_U16 XP_CHAR16; + +typedef enum { + BONUS_NONE, + BONUS_DOUBLE_LETTER, + BONUS_DOUBLE_WORD, + BONUS_TRIPLE_LETTER, + BONUS_TRIPLE_WORD, + + BONUS_LAST +} XWBonusType; + +typedef enum { + INTRADE_MW_TEXT = BONUS_LAST +} XWMiniTextType; + +typedef struct SpecialBitmaps { + XP_Bitmap largeBM; + XP_Bitmap smallBM; +} SpecialBitmaps; + + +struct DictionaryCtxt { + void (*destructor)( DictionaryCtxt* dict ); + array_edge* topEdge; + array_edge* base; /* the physical beginning of the dictionary; not + necessarily the entry point for search!! */ + XP_UCHAR* name; + XP_CHAR16* faces16; /* 16 for unicode */ + XP_U8* countsAndValues; + + SpecialBitmaps* bitmaps; + XP_UCHAR** chars; + + XP_U8 nFaces; +#ifdef NODE_CAN_4 + XP_U8 nodeSize; + XP_U8 charSize; +#endif + + XP_S8 blankTile; /* negative means there's no known blank */ +#ifdef DEBUG + XP_U32 numEdges; +#endif + MPSLOT +}; + +/* This is the datastructure that allows access to a DAWG in a + * platform-independent way. + */ +/* typedef struct DictionaryVtable { */ +/* XP_U16 (*m_getTileValue)( DictionaryCtxt* ctxt, CellTile tile ); */ +/* unsigned char (*m_getTileChar)( DictionaryCtxt* ctxt, CellTile tile, */ +/* XP_FontCode* fontCode ); */ +/* XP_U16 (*m_numTiles)( DictionaryCtxt* ctxt, Tile tile ); */ +/* XP_U16 (*m_numTileFaces)( DictionaryCtxt* ctxt ); */ +/* } DictionaryVtable; */ + + +/* struct DictionaryCtxt { */ +/* DictionaryVtable* vtable; */ +/* }; */ + +/* #define dict_getTileValue(dc,t) \ */ +/* (dc)->vtable->m_getTileValue((dc),(t)) */ + +/* #define dict_getTileChar(dc,t,fc) \ */ +/* (dc)->vtable->m_getTileChar((dc),(t),(fc)) */ + +/* #define dict_numTiles(dc,t) (dc)->vtable->m_numTiles((dc),(t)) */ + +/* #define dict_numTileFaces(dc) (dc)->vtable->m_numTileFaces(dc) */ + +#define dict_destroy(d) (*((d)->destructor))(d) + +XP_Bool dict_tilesAreSame( DictionaryCtxt* dict1, DictionaryCtxt* dict2 ); + +XP_Bool dict_hasBlankTile( DictionaryCtxt* dict ); +Tile dict_getBlankTile( DictionaryCtxt* dict ); +XP_U16 dict_getTileValue( DictionaryCtxt* ctxt, Tile tile ); +XP_U16 dict_numTiles( DictionaryCtxt* ctxt, Tile tile ); +XP_U16 dict_numTileFaces( DictionaryCtxt* ctxt ); + +XP_U16 dict_tilesToString( DictionaryCtxt* ctxt, Tile* tiles, XP_U16 nTiles, + XP_UCHAR* buf ); +XP_UCHAR* dict_getName( DictionaryCtxt* ctxt ); + +Tile dict_tileForString( DictionaryCtxt* dict, XP_UCHAR* key ); + +XP_Bool dict_faceIsBitmap( DictionaryCtxt* dict, Tile tile ); +XP_Bitmap dict_getFaceBitmap( DictionaryCtxt* dict, Tile tile, + XP_Bool isLarge ); + +void dict_writeToStream( DictionaryCtxt* ctxt, XWStreamCtxt* stream ); +void dict_loadFromStream( DictionaryCtxt* dict, XWStreamCtxt* stream ); + + +/* These methods get "overridden" by subclasses. That is, they must be + implemented by each platform. */ + +array_edge* dict_edge_for_index( DictionaryCtxt* dict, XP_U32 index ); + +#ifdef OVERRIDE_GETTOPEDGE +/* platform code will implement this */ +array_edge* dict_getTopEdge( DictionaryCtxt* dict ); +#else +# define dict_getTopEdge( dict ) ((dict)->topEdge) +#endif + + +#ifdef STUBBED_DICT +DictionaryCtxt* make_stubbed_dict( MPFORMAL_NOCOMMA ); +#endif + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/dictnryp.h b/xwords4/common/dictnryp.h new file mode 100644 index 000000000..646bf67f5 --- /dev/null +++ b/xwords4/common/dictnryp.h @@ -0,0 +1,36 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2000 by Eric House (fixin@peak.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. + */ + + +#ifndef _DICTNRYP_H_ +#define _DICTNRYP_H_ + +#include "dictnry.h" + +#ifdef CPLUS +extern "C" { +#endif + +void setBlankTile( DictionaryCtxt* dctx ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/draw.c b/xwords4/common/draw.c new file mode 100644 index 000000000..e3025b180 --- /dev/null +++ b/xwords4/common/draw.c @@ -0,0 +1,370 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2003 by Eric House (fixin@peak.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. + */ + +#ifdef DRAW_WITH_PRIMITIVES + +#include "draw.h" +#include "xptypes.h" + +static void +insetRect( XP_Rect* r, XP_S16 amt ) +{ + r->top += amt; + r->left += amt; + amt *= 2; + r->width -= amt; + r->height -= amt; +} /* insetRect */ + +static void +getRemText( XP_UCHAR* buf, XP_U16 bufSize, XP_S16 nTilesLeft ) +{ + if ( nTilesLeft > 0 ) { + XP_SNPRINTF( buf, bufSize, "rem: %d", nTilesLeft ); + } else { + buf[0] = '\0'; + } +} /* getRemText */ + +static void +default_draw_measureRemText( DrawCtx* dctx, XP_Rect* r, + XP_S16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_U16 width, height; + + if ( nTilesLeft > 0 ) { + XP_UCHAR buf[20]; + getRemText( buf, sizeof(buf), nTilesLeft ); + draw_measureText( dctx, buf, &width, &height ); + } else { + width = height = 0; + } + + *widthP = width; + *heightP = height; +} /* default_draw_measureRemText */ + +static void +default_draw_drawRemText( DrawCtx* dctx, XP_Rect* rInner, + XP_Rect* rOuter, XP_S16 nTilesLeft ) +{ + XP_Rect oldClip; + XP_UCHAR buf[10]; + + getRemText( buf, sizeof(buf), nTilesLeft ); + + draw_setClip( dctx, rOuter, &oldClip ); + draw_drawString( dctx, buf, rOuter->left, rOuter->top ); + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawRemText */ + +static void +formatScore( XP_UCHAR* buf, XP_U16 bufSize, DrawScoreInfo* dsi ) +{ + XP_UCHAR remBuf[10]; + XP_UCHAR* selStr; + + if ( dsi->selected ) { + selStr = "*"; + } else { + selStr = ""; + } + + if ( dsi->nTilesLeft >= 0 ) { + XP_SNPRINTF( remBuf, sizeof(remBuf), ":%d", dsi->nTilesLeft ); + } else { + remBuf[0] = '\0'; + } + + XP_SNPRINTF( buf, bufSize, "%s%d%s%s", selStr, dsi->score, + remBuf, selStr ); +} /* formatScore */ + +static void +default_draw_measureScoreText( DrawCtx* dctx, XP_Rect* r, + DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_UCHAR buf[20]; + formatScore( buf, sizeof(buf), dsi ); + draw_measureText( dctx, buf, widthP, heightP ); +} /* default_draw_measureScoreText */ + +static void +default_draw_score_drawPlayer( DrawCtx* dctx, + XP_S16 playerNum, /* -1: don't use */ + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ) +{ + XP_Rect oldClip; + XP_UCHAR buf[20]; + + draw_setClip( dctx, rInner, &oldClip ); + draw_clearRect( dctx, rOuter ); + + formatScore( buf, sizeof(buf), dsi ); + draw_drawString( dctx, buf, rInner->left, rInner->top ); + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_score_drawPlayer */ + +static XP_Bool +default_draw_drawCell( DrawCtx* dctx, XP_Rect* rect, + /* at least one of these two will be null */ + XP_UCHAR* text, XP_Bitmap bitmap, + XP_S16 owner, /* -1 means don't use */ + XWBonusType bonus, XP_Bool isBlank, + XP_Bool highlight, XP_Bool isStar) +{ + XP_Rect oldClip; + XP_Rect inset = *rect; + insetRect( &inset, 1 ); + + draw_setClip( dctx, rect, &oldClip ); + + draw_clearRect( dctx, rect ); + + if ( !!text && text[0] != 0 ) { + draw_drawString( dctx, text, inset.left, inset.top ); + } else if ( !!bitmap ) { + draw_drawBitmap( dctx, bitmap, inset.left, inset.top ); + } else if ( bonus != BONUS_NONE ) { + XP_UCHAR* bstr; + switch( bonus ) { + case BONUS_DOUBLE_LETTER: + bstr = "*"; + break; + case BONUS_DOUBLE_WORD: + bstr = "%"; + break; + case BONUS_TRIPLE_LETTER: + bstr = "#"; + break; + case BONUS_TRIPLE_WORD: + bstr = "@"; + break; + default: + XP_ASSERT(0); + break; + } + draw_drawString( dctx, bstr, inset.left, inset.top ); + } + + if ( highlight ) { + draw_invertRect( dctx, &inset ); + } + + draw_frameRect( dctx, rect ); + draw_setClip( dctx, &oldClip, NULL ); + + return XP_TRUE; +} /* default_draw_drawCell */ + +static void +default_draw_drawBoardArrow( DrawCtx* dctx, XP_Rect* rect, + XWBonusType bonus, XP_Bool vert ) +{ + XP_Rect oldClip; + XP_UCHAR* arrow; + + if ( vert ) { + arrow = "|"; + } else { + arrow = "-"; + } + + draw_setClip( dctx, rect, &oldClip ); + draw_clearRect( dctx, rect ); + draw_frameRect( dctx, rect ); + draw_drawString( dctx, arrow, rect->left+1, rect->top+1 ); + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawBoardArrow */ + +static void +default_draw_drawTile( DrawCtx* dctx, XP_Rect* rect, + /* at least 1 of these two will be null*/ + XP_UCHAR* text, XP_Bitmap bitmap, + XP_S16 val, XP_Bool highlighted ) +{ + XP_Rect oldClip; + XP_Rect inset = *rect; + + draw_setClip( dctx, rect, &oldClip ); + draw_clearRect( dctx, rect ); + + draw_frameRect( dctx, rect ); + + if ( highlighted ) { + insetRect( &inset, 1 ); + draw_frameRect( dctx, &inset ); + insetRect( &inset, 1 ); + } else { + insetRect( &inset, 2 ); + } + + if ( !!text && text[0] != '\0' ) { + draw_drawString( dctx, text, inset.left, inset.top ); + } else if ( !!bitmap ) { + draw_drawBitmap( dctx, bitmap, inset.left, inset.top ); + } + + if ( val >= 0 ) { + XP_UCHAR sbuf[4]; + XP_U16 width, height; + XP_U16 x, y; + + XP_SNPRINTF( sbuf, sizeof(sbuf), "%d", val ); + draw_measureText( dctx, sbuf, &width, &height ); + + x = inset.left + inset.width - width; + y = inset.top + inset.height - height; + draw_drawString( dctx, sbuf, x, y ); + } + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawTile */ + +static void +default_draw_drawTileBack( DrawCtx* dctx, XP_Rect* rect ) +{ + default_draw_drawTile( dctx, rect, "?", NULL, -1, XP_FALSE ); +} /* default_draw_drawTileBack */ + +static void +default_draw_drawTrayDivider( DrawCtx* dctx, XP_Rect* rect, + XP_Bool selected) +{ + XP_Rect r = *rect; + draw_clearRect( dctx, rect ); + if ( r.width > 2 ) { + r.width -= 2; + r.left += 1; + } + draw_frameRect( dctx, &r ); +} /* default_draw_drawTrayDivider */ + +static void +default_draw_score_pendingScore( DrawCtx* dctx, XP_Rect* rect, + XP_S16 score, XP_U16 playerNum ) +{ + XP_UCHAR buf[5]; + XP_Rect oldClip; + XP_Rect r; + XP_U16 width, height; + XP_UCHAR* stxt; + + draw_setClip( dctx, rect, &oldClip ); + + XP_MEMCPY( &r, rect, sizeof(r) ); + ++r.left; /* don't erase neighbor's border */ + --r.width; + draw_clearRect( dctx, &r ); + + draw_drawString( dctx, "pts", r.left, r.top ); + + if ( score >= 0 ) { + XP_SNPRINTF( buf, sizeof(buf), "%d", score ); + stxt = buf; + } else { + stxt = "???"; + } + draw_measureText( dctx, stxt, &width, &height ); + draw_drawString( dctx, stxt, r.left, r.top + r.height - height ); + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_score_pendingScore */ + +static XP_UCHAR* +default_draw_getMiniWText( DrawCtx* dctx, XWMiniTextType textHint ) +{ + unsigned char* str; + + switch( textHint ) { + case BONUS_DOUBLE_LETTER: + str = "Double letter"; break; + case BONUS_DOUBLE_WORD: + str = "Double word"; break; + case BONUS_TRIPLE_LETTER: + str = "Triple letter"; break; + case BONUS_TRIPLE_WORD: + str = "Triple word"; break; + case INTRADE_MW_TEXT: + str = "Trading tiles;\nclick D when done"; break; + default: + XP_ASSERT( XP_FALSE ); + } + return str; +} /* default_draw_getMiniWText */ + +static void +default_draw_measureMiniWText( DrawCtx* dctx, XP_UCHAR* textP, + XP_U16* widthP, XP_U16* heightP ) +{ + draw_measureText( dctx, textP, widthP, heightP ); + + /* increase for frame */ + *widthP += 2; + *heightP += 2; +} /* default_draw_measureMiniWText */ + +static void +default_draw_drawMiniWindow( DrawCtx* dctx, XP_UCHAR* text, + XP_Rect* rect, void** closure ) +{ + XP_Rect oldClip; + + draw_setClip( dctx, rect, &oldClip ); + + draw_clearRect( dctx, rect ); + draw_frameRect( dctx, rect ); + draw_drawString( dctx, text, rect->left+1, rect->top+1 ); + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawMiniWindow */ + +static void +default_draw_eraseMiniWindow( DrawCtx* dctx, XP_Rect* rect, + XP_Bool lastTime, void** closure, + XP_Bool* invalUnder ) +{ + *invalUnder = XP_TRUE; /* let board.c do the work */ +} /* default_draw_eraseMiniWindow */ + +void +InitDrawDefaults( DrawCtxVTable* vtable ) +{ + SET_VTABLE_ENTRY( vtable, draw_measureRemText, default ); + SET_VTABLE_ENTRY( vtable, draw_drawRemText, default ); + SET_VTABLE_ENTRY( vtable, draw_measureScoreText, default ); + SET_VTABLE_ENTRY( vtable, draw_score_drawPlayer, default ); + SET_VTABLE_ENTRY( vtable, draw_drawCell, default ); + SET_VTABLE_ENTRY( vtable, draw_drawBoardArrow, default ); + SET_VTABLE_ENTRY( vtable, draw_drawTile, default ); + SET_VTABLE_ENTRY( vtable, draw_drawTileBack, default ); + SET_VTABLE_ENTRY( vtable, draw_drawTrayDivider, default ); + SET_VTABLE_ENTRY( vtable, draw_score_pendingScore, default ); + + SET_VTABLE_ENTRY( vtable, draw_getMiniWText, default ); + SET_VTABLE_ENTRY( vtable, draw_measureMiniWText, default ); + SET_VTABLE_ENTRY( vtable, draw_drawMiniWindow, default ); + SET_VTABLE_ENTRY( vtable, draw_eraseMiniWindow, default ); +} /* InitDrawDefaults */ + +#endif diff --git a/xwords4/common/draw.h b/xwords4/common/draw.h new file mode 100644 index 000000000..e3541d79b --- /dev/null +++ b/xwords4/common/draw.h @@ -0,0 +1,242 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (fixin@peak.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. + */ + +#ifndef _DRAW_H_ +#define _DRAW_H_ + +#include "comtypes.h" +#include "xptypes.h" +#include "model.h" + +/* typedef struct DrawCtx DrawCtx; */ + +typedef struct DrawScoreInfo { + void* closure; + XP_UCHAR* name; + XP_S16 score; + XP_S16 nTilesLeft; /* < 0 means don't use */ + XP_Bool isTurn; + XP_Bool selected; + XP_Bool isRemote; + XP_Bool isRobot; +} DrawScoreInfo; + +typedef struct DrawCtxVTable { + +#ifdef DRAW_WITH_PRIMITIVES + void (*m_draw_setClip)( DrawCtx* dctx, XP_Rect* newClip, + XP_Rect* oldClip ); + void (*m_draw_frameRect)( DrawCtx* dctx, XP_Rect* rect ); + void (*m_draw_invertRect)( DrawCtx* dctx, XP_Rect* rect ); + void (*m_draw_drawString)( DrawCtx* dctx, XP_UCHAR* str, + XP_U16 x, XP_U16 y ); + void (*m_draw_drawBitmap)( DrawCtx* dctx, XP_Bitmap bm, + XP_U16 x, XP_U16 y ); + void (*m_draw_measureText)( DrawCtx* dctx, XP_UCHAR* buf, + XP_U16* widthP, XP_U16* heightP ); +#endif + + void (*m_draw_destroyCtxt)( DrawCtx* dctx ); + + void (*m_draw_boardBegin)( DrawCtx* dctx, XP_Rect* rect, + XP_Bool hasfocus ); + void (*m_draw_boardFinished)( DrawCtx* dctx ); + + XP_Bool (*m_draw_vertScrollBoard)(DrawCtx* dctx, XP_Rect* rect, + XP_S16 dist ); + + void (*m_draw_trayBegin)( DrawCtx* dctx, XP_Rect* rect, + XP_U16 owner, XP_Bool hasfocus ); + void (*m_draw_trayFinished)( DrawCtx* dctx ); + + void (*m_draw_measureRemText)( DrawCtx* dctx, XP_Rect* r, + XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ); + void (*m_draw_drawRemText)(DrawCtx* dctx, XP_Rect* rInner, + XP_Rect* rOuter, XP_S16 nTilesLeft); + + void (*m_draw_scoreBegin)( DrawCtx* dctx, XP_Rect* rect, + XP_U16 numPlayers, XP_Bool hasfocus ); + void (*m_draw_measureScoreText)( DrawCtx* dctx, XP_Rect* r, + DrawScoreInfo* dsi, + XP_U16* width, XP_U16* height ); + void (*m_draw_score_drawPlayer)( DrawCtx* dctx, + XP_S16 playerNum, /* -1: don't use */ + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ); + + void (*m_draw_score_pendingScore)( DrawCtx* dctx, XP_Rect* rect, + XP_S16 score, XP_U16 playerNum ); + + void (*m_draw_scoreFinished)( DrawCtx* dctx ); + + void (*m_draw_drawTimer)( DrawCtx* dctx, XP_Rect* rInner, XP_Rect* rOuter, + XP_U16 player, XP_S16 secondsLeft ); + + XP_Bool (*m_draw_drawCell)( DrawCtx* dctx, XP_Rect* rect, + /* at least one of these two will be null */ + XP_UCHAR* text, XP_Bitmap bitmap, + XP_S16 owner, /* -1 means don't use */ + XWBonusType bonus, XP_Bool isBlank, + XP_Bool highlight, XP_Bool isStar); + + void (*m_draw_invertCell)( DrawCtx* dctx, XP_Rect* rect ); + + void (*m_draw_drawTile)( DrawCtx* dctx, XP_Rect* rect, + /* at least 1 of these two will be null*/ + XP_UCHAR* text, XP_Bitmap bitmap, + XP_S16 val, XP_Bool highlighted ); + void (*m_draw_drawTileBack)( DrawCtx* dctx, XP_Rect* rect ); + void (*m_draw_drawTrayDivider)( DrawCtx* dctx, XP_Rect* rect, + XP_Bool selected ); + + void (*m_draw_clearRect)( DrawCtx* dctx, XP_Rect* rect ); + + void (*m_draw_drawBoardArrow)( DrawCtx* dctx, XP_Rect* rect, + XWBonusType bonus, XP_Bool vert ); +#ifdef KEY_SUPPORT + void (*m_draw_drawTrayCursor)( DrawCtx* dctx, XP_Rect* rect ); + void (*m_draw_drawBoardCursor)( DrawCtx* dctx, XP_Rect* rect ); +#endif + + XP_UCHAR* (*m_draw_getMiniWText)( DrawCtx* dctx, + XWMiniTextType textHint ); + void (*m_draw_measureMiniWText)( DrawCtx* dctx, XP_UCHAR* textP, + XP_U16* width, XP_U16* height ); + void (*m_draw_drawMiniWindow)( DrawCtx* dctx, XP_UCHAR* text, + XP_Rect* rect, void** closure ); + void (*m_draw_eraseMiniWindow)( DrawCtx* dctx, XP_Rect* rect, + XP_Bool lastTime, void** closure, + XP_Bool* invalUnder ); + +} DrawCtxVTable; /* */ + +struct DrawCtx { + DrawCtxVTable* vtable; +}; + +#define draw_destroyCtxt(dc) \ + (dc)->vtable->m_draw_destroyCtxt(dc) + +#define draw_boardBegin( dc, r, f ) \ + (dc)->vtable->m_draw_boardBegin((dc), (r), (f)) +#define draw_boardFinished( dc ) \ + (dc)->vtable->m_draw_boardFinished(dc) + +#define draw_trayBegin( dc, r, o, f ) \ + (dc)->vtable->m_draw_trayBegin((dc), (r), (o), (f)) +#define draw_trayFinished( dc ) \ + (dc)->vtable->m_draw_trayFinished(dc) + +#define draw_vertScrollBoard( dc, r, d ) \ + (dc)->vtable->m_draw_vertScrollBoard((dc),(r),(d)) + +#define draw_scoreBegin( dc, r, t, f ) \ + (dc)->vtable->m_draw_scoreBegin((dc), (r), (t), (f)) + +#define draw_measureRemText( dc, r, n, wp, hp ) \ + (dc)->vtable->m_draw_measureRemText( (dc), (r), (n), (wp), (hp) ) + +#define draw_drawRemText( dc, ri, ro, n ) \ + (dc)->vtable->m_draw_drawRemText( (dc), (ri), (ro), (n) ) + +#define draw_measureScoreText(dc,r,dsi,wp,hp) \ + (dc)->vtable->m_draw_measureScoreText((dc),(r),(dsi),(wp),(hp)) + +#define draw_score_drawPlayer(dc, i, ri, ro, dsi) \ + (dc)->vtable->m_draw_score_drawPlayer((dc),(i),(ri),(ro),(dsi)) + +#define draw_score_pendingScore(dc, r, s, p ) \ + (dc)->vtable->m_draw_score_pendingScore((dc), (r), (s), (p)) + +#define draw_scoreFinished( dc ) \ + (dc)->vtable->m_draw_scoreFinished(dc) + +#define draw_drawTimer( dc, ri, ro, plyr, sec ) \ + (dc)->vtable->m_draw_drawTimer((dc),(ri),(ro),(plyr),(sec)) + +/* #define draw_frameBoard( dc, rect ) \ */ +/* (dc)->vtable->m_draw_frameBoard((dc),(rect)) */ +/* #define draw_frameTray( dc, rect ) (dc)->vtable->m_draw_frameTray((dc),(rect)) */ + +#define draw_drawCell( dc, rect, txt, bmap, o, bon, bl, h, s ) \ + (dc)->vtable->m_draw_drawCell((dc),(rect),(txt),(bmap),(o),(bon),\ + (bl),(h),(s)) + +#define draw_invertCell( dc, rect ) \ + (dc)->vtable->m_draw_invertCell((dc),(rect)) + +#define draw_drawTile( dc, rect, text, bmp, val, hil ) \ + (dc)->vtable->m_draw_drawTile((dc),(rect),(text),(bmp),(val),(hil)) +#define draw_drawTileBack( dc, rect ) \ + (dc)->vtable->m_draw_drawTileBack( (dc), (rect) ) +#define draw_drawTrayDivider( dc, rect, s ) \ + (dc)->vtable->m_draw_drawTrayDivider((dc),(rect), (s)) + +#define draw_clearRect( dc, rect ) (dc)->vtable->m_draw_clearRect((dc),(rect)) + +#define draw_drawBoardArrow( dc, r, b, v ) \ + (dc)->vtable->m_draw_drawBoardArrow((dc),(r),(b), (v)) +#ifdef KEY_SUPPORT +# define draw_drawTrayCursor( dc, r ) \ + (dc)->vtable->m_draw_drawTrayCursor((dc),(r)) +# define draw_drawBoardCursor( dc, r ) \ + (dc)->vtable->m_draw_drawBoardCursor((dc),(r)) +#else +# define draw_drawTrayCursor( dc, r ) +# define draw_drawBoardCursor( dc, r ) +#endif + +#define draw_getMiniWText( dc, b ) \ + (dc)->vtable->m_draw_getMiniWText( (dc),(b) ) + +#define draw_measureMiniWText( dc, t, wp, hp) \ + (dc)->vtable->m_draw_measureMiniWText( (dc),(t), (wp), (hp) ) + +#define draw_drawMiniWindow( dc, t, r, c ) \ + (dc)->vtable->m_draw_drawMiniWindow( (dc), (t), (r), (c) ) + +#define draw_eraseMiniWindow(dc, r, l, c, b) \ + (dc)->vtable->m_draw_eraseMiniWindow( (dc), (r), (l), (c), (b) ) + +#ifdef DRAW_WITH_PRIMITIVES +#define draw_setClip( dc, rn, ro ) \ + (dc)->vtable->m_draw_setClip( (dc), (rn), (ro) ) + +#define draw_frameRect( dc, r ) \ + (dc)->vtable->m_draw_frameRect( (dc), (r) ) + +#define draw_invertRect( dc, r ) \ + (dc)->vtable->m_draw_invertCell( (dc), (r) ) + +#define draw_drawString( dc, s, x, y ) \ + (dc)->vtable->m_draw_drawString( (dc), (s), (x), (y) ) + +#define draw_drawBitmap( dc, bm, x, y ) \ + (dc)->vtable->m_draw_drawBitmap( (dc), (bm), (x), (y) ) + +#define draw_measureText( dc, t, wp, hp ) \ + (dc)->vtable->m_draw_measureText( (dc), (t), (wp), (hp) ) + + +void InitDrawDefaults( DrawCtxVTable* vtable ); +#endif /* DRAW_WITH_PRIMITIVES */ + + +#endif diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c new file mode 100644 index 000000000..a4a8177d8 --- /dev/null +++ b/xwords4/common/engine.c @@ -0,0 +1,1105 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (fixin@peak.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 "comtypes.h" +#include "engine.h" +#include "dictnry.h" +#include "util.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define eEND 0x63454e44 + +typedef XP_U8 Engine_rack[MAX_UNIQUE_TILES+1]; + +#define NUM_SAVED_MOVES 10 + +typedef struct BlankTuple { + short col; + Tile tile; +} BlankTuple; + +typedef struct PossibleMove { + XP_U16 score; /* Because I'm doing a memcmp to sort these things, + the comparison must be done differently on + little-endian platforms. */ + MoveInfo moveInfo; + //XP_U16 whichBlanks; /* flags */ + Tile blankVals[MAX_COLS]; /* the faces for which we've substituted + blanks */ +} PossibleMove; + +typedef struct MoveIterationData { + PossibleMove savedMoves[NUM_SAVED_MOVES]; + XP_U16 lowestSavedScore; + PossibleMove lastSeenMove; + XP_U16 leftInMoveCache; +} MoveIterationData; + +typedef XP_U32 Crosscheck; /* one bit per tile that's possible here */ + +struct EngineCtxt { + ModelCtxt* model; + DictionaryCtxt* dict; + XW_UtilCtxt* util; + + Engine_rack rack; + Tile blankTile; + XP_Bool searchInProgress; + XP_Bool searchHorizontal; + XP_Bool isRobot; + XP_U16 numRows, numCols; + XP_U16 curRow; + XP_U16 blankCount; + XP_U16 targetScore; /* not used at the moment */ + XP_U16 star_row; + XP_Bool returnNOW; + MoveIterationData miData; + + XP_S16 blankValues[MAX_TRAY_TILES]; + Crosscheck rowChecks[MAX_ROWS]; // also used in xwscore + XP_U16 scoreCache[MAX_ROWS]; + +#ifdef DEBUG + XP_U16 curLimit; +#endif + MPSLOT +}; /* EngineCtxt */ + +static void findMovesOneRow( EngineCtxt* engine ); +static Tile localGetBoardTile( EngineCtxt* engine, XP_U16 col, + XP_U16 row, XP_Bool substBlank ); +static array_edge* edge_with_tile( DictionaryCtxt* dict, array_edge* from, Tile tile ); +static XP_Bool scoreQualifies( EngineCtxt* engine, XP_U16 score ); +static void findMovesForAnchor( EngineCtxt* engine, XP_S16* prevAnchor, + XP_U16 col, XP_U16 row ) ; +static Crosscheck figureCrosschecks( EngineCtxt* engine, XP_U16 col, + XP_U16 row, XP_U16* scoreP ); +static XP_Bool isAnchorSquare( EngineCtxt* engine, XP_U16 col, XP_U16 row ); +static array_edge* follow( DictionaryCtxt* dict, array_edge* in ); +static array_edge* edge_from_tile( DictionaryCtxt* dict, array_edge* from, + Tile tile ); +static void leftPart( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_U16 limit, XP_U16 firstCol, + XP_U16 anchorCol, XP_U16 row ); +static void extendRight( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_Bool accepting, + XP_U16 firstCol, XP_U16 col, XP_U16 row ); +static array_edge* consumeFromLeft( EngineCtxt* engine, array_edge* edge, + short col, short row ); +static XP_Bool rack_remove( EngineCtxt* engine, Tile tile, XP_Bool* isBlank ); +static void rack_replace( EngineCtxt* engine, Tile tile, XP_Bool isBlank ); +static void considerMove( EngineCtxt* engine, Tile* tiles, short tileLength, + short firstCol, short lastCol, short lastRow ); +static void considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, + PossibleMove* posmove, + XP_U16 lastRow, + BlankTuple* usedBlanks, + XP_U16 usedBlanksCount ); +static void saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ); + + + +#if defined __LITTLE_ENDIAN +static XP_S16 cmpMoves( PossibleMove* m1, PossibleMove* m2 ); +# define CMPMOVES( m1, m2 ) cmpMoves( m1, m2 ) +#elif defined __BIG_ENDIAN +# define CMPMOVES( m1, m2 ) XP_MEMCMP( m1, m2, sizeof(*(m1))) +#else + error: need to pick one!!! +#endif + +#define ISACCEPTING(d,e) ((ACCEPTINGMASK & ((array_edge_old*)(e))->bits) != 0) +#define IS_LAST_EDGE(d,e) ((LASTEDGEMASK & ((array_edge_old*)(e))->bits) != 0) +#define EDGETILE(d,edge) ((Tile)(((array_edge_old*)(edge))->bits & LETTERMASK)) +#define CROSSCHECK_CONTAINS(chk,tile) (((chk) & (1L<<(tile))) != 0) + +#define HILITE_CELL( engine, col, row ) \ + util_hiliteCell( (engine)->util, (col), (row) ) + +/* not implemented yet */ +XP_U16 +engine_getScoreCache( EngineCtxt* engine, XP_U16 row ) +{ + return engine->scoreCache[row]; +} /* engine_getScoreCache */ + +/***************************************************************************** + * This should be the first executable code in the file in case I want to + * turn it into a separate code module later. + ****************************************************************************/ +EngineCtxt* +engine_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isRobot ) +{ + EngineCtxt* result = (EngineCtxt*)XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + result->util = util; + + result->isRobot = isRobot; + + engine_reset( result ); + + return result; +} /* engine_make */ + +void +engine_writeToStream( EngineCtxt* ctxt, XWStreamCtxt* stream ) +{ + /* nothing to save; see comment below */ +#ifdef DEBUG + stream_putU32( stream, eEND ); +#endif +} /* engine_writeToStream */ + +EngineCtxt* +engine_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, + XP_Bool isRobot ) +{ + EngineCtxt* engine = engine_make( MPPARM(mpool) util, isRobot ); + + /* All the engine's data seems to be used only in the process of finding a + move. So if we're willing to have the set of moves found lost across + a save, there's nothing to do! */ + + XP_ASSERT( stream_getU32( stream ) == eEND ); + + return engine; +} /* engine_makeFromStream */ + +void +engine_reset( EngineCtxt* engine ) +{ + XP_MEMSET( &engine->miData, 0, sizeof(engine->miData) ); + engine->miData.lastSeenMove.score = 0xffff; /* max possible */ + engine->searchInProgress = XP_FALSE; +} /* engine_reset */ + +void +engine_destroy( EngineCtxt* engine ) +{ + XP_ASSERT( engine != NULL ); + XP_FREE( engine->mpool, engine ); +} /* engine_destroy */ + +static XP_Bool +initTray( EngineCtxt* engine, const Tile* tiles, XP_U16 numTiles ) +{ + XP_Bool result = numTiles > 0; + XP_U16 i; + + if ( result ) { + XP_MEMSET( engine->rack, 0, sizeof(engine->rack) ); + for ( i = 0; i < numTiles; ++i ) { + Tile tile = *tiles++; + XP_ASSERT( tile <= MAX_UNIQUE_TILES+1 ); + ++engine->rack[tile]; + } + } + + return result; +} /* initTray */ + +static void +findFirstMoves( EngineCtxt* engine ) +{ + XP_S16 prevAnchor = -1; + XP_U16 star_row = engine->star_row; + + /* all have trivial crosschecks */ + XP_MEMSET( engine->rowChecks, 0xFF, sizeof(engine->rowChecks) ); + + /* middle square is the only legal anchor */ + engine->searchHorizontal = XP_TRUE; + findMovesForAnchor( engine, &prevAnchor, star_row, star_row ); + /* util_hiliteCell( engine->util, STAR_ROW, STAR_ROW ); */ + HILITE_CELL( engine, star_row, star_row ); +} /* findFirstMoves */ + +#if defined __LITTLE_ENDIAN +static XP_S16 +cmpMoves( PossibleMove* m1, PossibleMove* m2 ) +{ + if ( m1->score == m2->score ) { + return XP_MEMCMP( &m1->moveInfo, &m2->moveInfo, + sizeof(*m1) - sizeof( m1->score ) ); + } else if ( m1->score < m2->score ) { + return -1; + } else { + return 1; + } +} /* cmpMoves */ +#endif + +static XP_Bool +chooseMove( EngineCtxt* engine, PossibleMove** move ) +{ + XP_U16 i; + PossibleMove* chosen; + + /* First, sort 'em. Put the higher-scoring moves at the top where they'll + get picked up first. Don't sort if we're working for a robot; we've + only been saving the single best move anyway. At least not until we + start applying other criteria than score to moves. */ + if ( engine->isRobot ) { + chosen = &engine->miData.savedMoves[0]; + } else { + while ( engine->miData.leftInMoveCache == 0 ) { + XP_Bool done = XP_TRUE; + for ( i = 0; i < NUM_SAVED_MOVES-1; ++i ) { + if ( CMPMOVES( &engine->miData.savedMoves[i], + &engine->miData.savedMoves[i+1]) > 0 ) { + PossibleMove tmp; + XP_MEMCPY( &tmp, &engine->miData.savedMoves[i], + sizeof(tmp) ); + XP_MEMCPY( &engine->miData.savedMoves[i], + &engine->miData.savedMoves[i+1], + sizeof(engine->miData.savedMoves[i]) ); + XP_MEMCPY( &engine->miData.savedMoves[i+1], &tmp, + sizeof(engine->miData.savedMoves[i+1]) ); + done = XP_FALSE; + } + } + if ( done ) { + engine->miData.leftInMoveCache = NUM_SAVED_MOVES; + } +#if 0 + XP_DEBUGF( "sorted moves; scores are: " ); + for ( i = 0; i < NUM_SAVED_MOVES; ++i ) { + XP_DEBUGF( "%d; ", engine->miData.savedMoves[i].score ); + } + XP_DEBUGF( "\n" ); +#endif + } + /* now pick the one we're supposed to return */ + chosen = &engine->miData.savedMoves[--engine->miData.leftInMoveCache]; + } + + *move = chosen; /* set either way */ + + if ( chosen->score > 0 ) { + + if ( engine->miData.leftInMoveCache == 0 ) { + XP_MEMCPY( &engine->miData.lastSeenMove, + &engine->miData.savedMoves[0], + sizeof(engine->miData.lastSeenMove) ); + engine->miData.lowestSavedScore = 0; + } + + return XP_TRUE; + } else { + engine_reset( engine ); + return XP_FALSE; + } +} /* chooseMove */ + +/* Return of XP_TRUE means that we ran to completion. XP_FALSE means we were + * interrupted. Whether an actual move was found is indicated by what's + * filled in in *newMove. + */ +XP_Bool +engine_findMove( EngineCtxt* engine, ModelCtxt* model, + DictionaryCtxt* dict, const Tile* tiles, + XP_U16 numTiles, XP_U16 targetScore, XP_Bool* canMoveP, + MoveInfo* newMove ) +{ + XP_Bool result = XP_TRUE; + XP_Bool firstMove; + XP_U16 star_row; + + engine->model = model; + engine->dict = dict; + engine->blankTile = dict_getBlankTile( dict ); + engine->returnNOW = XP_FALSE; + + engine->star_row = star_row = model_numRows(model) / 2; + firstMove = EMPTY_TILE == localGetBoardTile( engine, star_row, star_row, + XP_FALSE ); + + /* If we've been asked to generate a move but can't because the + dictionary's emtpy or there are no tiles, still return TRUE so we don't + get scheduled again. Fixes infinite loop with empty dict and a + robot. */ + *canMoveP = dict_getTopEdge(dict) != NULL && initTray( engine, tiles, numTiles ); + if ( *canMoveP ) { + + util_engineStarting( engine->util ); + + engine->targetScore = targetScore; + + if ( engine->miData.leftInMoveCache == 0 ) { + + XP_MEMSET( engine->miData.savedMoves, 0, + sizeof(engine->miData.savedMoves) ); + + if ( firstMove ) { + findFirstMoves( engine ); + } else { + if ( engine->searchInProgress ) { + goto resumePoint; + } else { + engine->searchHorizontal = XP_TRUE; + engine->searchInProgress = XP_TRUE; + } + for ( ; ; ) { + engine->numRows = model_numRows(engine->model); + engine->numCols = model_numCols(engine->model); + if ( !engine->searchHorizontal ) { + XP_U16 tmp = engine->numRows; + engine->numRows = engine->numCols; + engine->numCols = tmp; + } + + for ( engine->curRow = 0; + engine->curRow < engine->numRows; + ++engine->curRow ) { + resumePoint: + findMovesOneRow( engine ); + if ( engine->returnNOW ) { + goto outer; + } + } + + if ( engine->searchHorizontal ) { + engine->searchHorizontal = XP_FALSE; + } else { + engine->searchInProgress = XP_FALSE; + break; + } + } /* forever */ + outer: + result = result; /* c++ wants a statement after the label */ + } /* if not firstMove */ + } + /* Search is finished. Choose (or just return) the best move found. */ + if ( engine->returnNOW ) { + result = XP_FALSE; + } else { + PossibleMove* move; + + result = XP_TRUE; + + (void)chooseMove( engine, &move ); + XP_ASSERT( !!newMove ); + XP_MEMCPY( newMove, &move->moveInfo, sizeof(*newMove) ); + } + + util_engineStopping( engine->util ); + } else { + /* set up a PASS. I suspect the caller should be deciding how to + handle this case itself, but this doesn't preclude its doing + so. */ + newMove->nTiles = 0; + } + + return result; +} /* engine_findMove */ + +static void +findMovesOneRow( EngineCtxt* engine ) +{ + XP_U16 lastCol = engine->numCols; + XP_U16 col, row = engine->curRow; + XP_S16 prevAnchor; + + for ( col = 0; col < lastCol; ++col ) { + engine->rowChecks[col] = + figureCrosschecks( engine, col, row, &engine->scoreCache[col] ); + /* XP_DEBUGF( "row %d: set scoreCache[%d] to %d\n", row, col, */ + /* engine->scoreCache[col] ); */ + } + + prevAnchor = -1; + for ( col = 0; col < lastCol && !engine->returnNOW; ++col ) { + if ( isAnchorSquare( engine, col, row ) ) { + findMovesForAnchor( engine, &prevAnchor, col, row ); + } + } +} /* findMovesOneRow */ + +static XP_Bool +lookup( DictionaryCtxt* dict, array_edge* edge, Tile* buf, XP_U16 tileIndex, + XP_U16 length ) +{ + while ( edge != NULL ) { + Tile targetTile = buf[tileIndex]; + edge = edge_with_tile( dict, edge, targetTile ); + if ( edge == NULL ) { /* tile not available out of this node */ + return XP_FALSE; + } else { + if ( ++tileIndex == length ) { /* is this the last tile? */ + return ISACCEPTING(dict, edge); + } else { + edge = follow( dict, edge ); + continue; + } + } + } + return XP_FALSE; +} /* lookup */ + +static Crosscheck +figureCrosschecks( EngineCtxt* engine, XP_U16 x, XP_U16 y, XP_U16* scoreP ) +{ + Crosscheck result = 0L; /* nothing's possible */ + XP_S16 startY, maybeY; + XP_U16 numRows = engine->numRows; + Tile tile; + array_edge* in_edge; + array_edge* candidateEdge; + Tile tiles[MAX_COLS]; + XP_U16 tilesAfter; + XP_U16 checkScore = 0; + DictionaryCtxt* dict = engine->dict; + + if ( localGetBoardTile( engine, x, y, XP_FALSE ) == EMPTY_TILE ) { + + /* find the first tile of any prefix */ + startY = (XP_S16)y; + for ( ; ; ) { + maybeY = startY - 1; + if ( maybeY < 0 ) { + break; + } + if ( localGetBoardTile( engine, x, maybeY, XP_FALSE ) + == EMPTY_TILE ) { + break; + } + startY = maybeY; + } + + /* Take care of the "special case" where the square has no neighbors + in either crosscheck direction */ + if ( (y == startY) && + ((y == numRows-1) || + (localGetBoardTile( engine, x, y+1, XP_FALSE ) == EMPTY_TILE))){ + result = 0xFFFFFFFF; /* all tiles legal and checkScore remains 0, + as there are no neighbors */ + goto outer; + } + + /* now walk the DAWG consuming any prefix. We want in_edge to wind up + holding the edge that leads to {x,y}, which will be the root edge + if there's no prefix. I can't use consumeFromLeft() here because + here I'm consuming upward. But I could generalize it.... */ + in_edge = dict_getTopEdge( dict ); + while ( startY < y ) { + tile = localGetBoardTile( engine, x, startY, XP_TRUE ); + XP_ASSERT( tile != EMPTY_TILE ); + checkScore += dict_getTileValue( dict, tile ); + tile = localGetBoardTile( engine, x, startY, XP_FALSE ); + in_edge = edge_from_tile( dict, in_edge, tile ); + /* If we run into a null edge here we have a prefix that by the + dictionary is an illegal word. One way it could have gotten + there is by being placed by a human. So it's not something to + flag here, but we won't be able to put anything in the spot so + the crosscheck is empty. And the ASSERT goes. + + Note that if we were disallowing words not in the dictionary + (as in a robot-only game) then the assertion would be valid: + only if there's a single letter as the "prefix" of our + crosscheck would it make sense for there to be no edge leading + out of it. But when can that happen? I.e. what letters don't + begin words in any reasonable word list? */ + if ( in_edge == NULL ) { + /* Only way to have gotten here is if a user's played a word + not in this dict. We'll not be able to build on it! */ + result = 0L; + goto outer; + } + ++startY; + } + + /* now in_edge points to the array of candidate edges. We'll build up + a buffer of the Tiles following the candidate square on the board, + then put each candidate edge's Tile in place and do a lookup + beginning at in_edge. Successful candidate tiles get added to the + Crosscheck */ + for ( tilesAfter = 1, maybeY = y + 1; maybeY < numRows; ++maybeY ) { + tile = localGetBoardTile( engine, x, maybeY, XP_TRUE ); + if ( tile == EMPTY_TILE ) { + break; + } else { + checkScore += dict_getTileValue( dict, tile ); + tiles[tilesAfter++] = localGetBoardTile( engine, x, maybeY, + XP_FALSE ); + } + } + + /* would it be possible to use extendRight here? With an empty + tray? No: it calls considerMove etc. */ + candidateEdge = in_edge; + for ( ; ; ) { + tile = EDGETILE( dict, candidateEdge ); + XP_ASSERT( tile < MAX_UNIQUE_TILES ); + tiles[0] = tile; + if ( lookup( dict, in_edge, tiles, 0, tilesAfter ) ) { + Crosscheck tmp = (1L << tile); + XP_ASSERT( tmp != 0 ); + result |= tmp; + } + + if ( IS_LAST_EDGE(dict,candidateEdge ) ) { + break; + } +#ifdef NODE_CAN_4 + candidateEdge += dict->nodeSize; +#else + candidateEdge += 3; +#endif + } + } + outer: + if ( scoreP != NULL ) { + *scoreP = checkScore; + } + return result; +} /* figureCrosschecks */ + +XP_Bool +engine_check( DictionaryCtxt* dict, Tile* tiles, XP_U16 nTiles ) +{ + array_edge* in_edge = dict_getTopEdge( dict ); + + return lookup( dict, in_edge, tiles, 0, nTiles ); +} /* engine_check */ + +static Tile +localGetBoardTile( EngineCtxt* engine, XP_U16 col, XP_U16 row, + XP_Bool substBlank ) +{ + Tile result; + XP_Bool isBlank, ignore; + + if ( !engine->searchHorizontal ) { + XP_U16 tmp = col; + col = row; + row = tmp; + } + + if ( model_getTile( engine->model, col, row, XP_FALSE, + 0, /* don't get pending, so turn doesn't matter */ + &result, &isBlank, &ignore, (XP_Bool*)NULL ) ) { + if ( isBlank && substBlank ) { + result = engine->blankTile; + } + return result; + } else { + return EMPTY_TILE; + } +} /* localGetBoardTile */ + +/***************************************************************************** + * Return true if the tile is empty and has a filled-in square on any of the + * four sides. + ****************************************************************************/ +static XP_Bool +isAnchorSquare( EngineCtxt* engine, XP_U16 col, XP_U16 row ) +{ + if ( localGetBoardTile( engine, col, row, XP_FALSE ) != EMPTY_TILE ) { + return XP_FALSE; + } + + if ( (col != 0) && + localGetBoardTile( engine, col-1, row, XP_FALSE ) != EMPTY_TILE ) { + return XP_TRUE; + } + if ( (col < engine->numCols-1) + && localGetBoardTile( engine, col+1, row, XP_FALSE ) != EMPTY_TILE) { + return XP_TRUE; + } + if ( (row != 0) + && localGetBoardTile( engine, col, row-1, XP_FALSE) != EMPTY_TILE ) { + return XP_TRUE; + } + if ( (row < engine->numRows-1) + && localGetBoardTile( engine, col, row+1, XP_FALSE ) != EMPTY_TILE ){ + return XP_TRUE; + } + return XP_FALSE; +} /* isAnchorSquare */ + +static void +hiliteForAnchor( EngineCtxt* engine, XP_U16 col, XP_U16 row ) +{ + if ( !engine->searchHorizontal ) { + XP_U16 tmp = col; + col = row; + row = tmp; + } + + if ( !util_hiliteCell( engine->util, col, row ) ) { + engine->returnNOW = XP_TRUE; + } +} /* hiliteForAnchor */ + +static void +findMovesForAnchor( EngineCtxt* engine, XP_S16* prevAnchor, + XP_U16 col, XP_U16 row ) +{ + XP_S16 limit; + array_edge* edge; + array_edge* topEdge; + Tile tiles[MAX_ROWS]; + XP_S16 tileLength = 0; + + hiliteForAnchor( engine, col, row ); + + if ( engine->returnNOW ) { + /* time to bail */ + } else { + limit = col - *prevAnchor - 1; +#ifdef TEST_MINLIMIT + if ( limit >= MAX_TRAY_TILES ) { + limit = MAX_TRAY_TILES - 1; + } +#endif + topEdge = dict_getTopEdge( engine->dict ); + if ( col == 0 ) { + edge = topEdge; + } else if ( localGetBoardTile( engine, col-1, row, XP_FALSE ) + == EMPTY_TILE ) { + leftPart( engine, tiles, 0, topEdge, limit, col, col, row ); + goto done; + } else { + edge = consumeFromLeft( engine, topEdge, col, row ); + } + DEBUG_ASSIGN(engine->curLimit, 0); + extendRight( engine, tiles, tileLength, edge, + XP_FALSE, // can't accept without the anchor square + col-limit, col, row ); + + done: + *prevAnchor = col; + } +} /* findMovesForAnchor */ + +static array_edge* +consumeFromLeft( EngineCtxt* engine, array_edge* edge, short col, short row ) +{ + XP_S16 maybeX; + Tile tile; + Tile tiles[MAX_ROWS]; + XP_U16 numTiles; + + /* Back up to the left until an empty tile or board edge is reached, saving + the tiles for cheaper retrieval as we walk forward through the DAWG. */ + for ( numTiles = 0, maybeX = col - 1; maybeX >= 0; --maybeX ) { + tile = localGetBoardTile( engine, maybeX, row, XP_FALSE ); + if ( tile == EMPTY_TILE ) { + break; + } + tiles[numTiles++] = tile; /* we're building the word backwards */ + } + XP_ASSERT( numTiles > 0 ); /* we should consume *something* */ + + /* could I just call lookup() here? Only if I fixed it to + communicate back the edge it's at after finishing. */ + while ( numTiles-- ) { + XP_ASSERT( tiles[numTiles] != EMPTY_TILE ); + + edge = edge_from_tile( engine->dict, edge, tiles[numTiles] ); + if ( edge == NULL ) { + break; + } + } + return edge; +} /* consumeFromLeft */ + +static void +leftPart( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_U16 limit, XP_U16 firstCol, + XP_U16 anchorCol, XP_U16 row ) +{ + DEBUG_ASSIGN( engine->curLimit, tileLength ); + + extendRight( engine, tiles, tileLength, edge, XP_FALSE, firstCol, + anchorCol, row ); + if ( !engine->returnNOW ) { + if ( (limit > 0) && (edge != NULL) ) { +#ifdef NODE_CAN_4 + XP_U16 nodeSize = engine->dict->nodeSize; +#endif + for ( ; ; ) { + XP_Bool isBlank; + Tile tile = EDGETILE( engine->dict, edge ); + if ( rack_remove( engine, tile, &isBlank ) ) { + tiles[tileLength] = tile; + leftPart( engine, tiles, tileLength+1, + follow( engine->dict, edge ), + limit-1, firstCol-1, anchorCol, row ); + rack_replace( engine, tile, isBlank ); + } + + if ( IS_LAST_EDGE( dict, edge ) || engine->returnNOW ) { + break; + } +#ifdef NODE_CAN_4 + edge += nodeSize; +#else + edge += 3; +#endif + + } + } + } +} /* leftPart */ + +static void +extendRight( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_Bool accepting, + XP_U16 firstCol, XP_U16 col, XP_U16 row ) +{ + Tile tile; + DictionaryCtxt* dict = engine->dict; + + if ( col == engine->numCols ) { /* we're off the board */ + goto check_exit; + } + tile = localGetBoardTile( engine, col, row, XP_FALSE ); + + if ( edge == NULL ) { // we're off the dictionary + if ( tile != EMPTY_TILE ) { + return; // don't check at the end + } + } else if ( tile == EMPTY_TILE ) { + Crosscheck check = engine->rowChecks[col]; /* make a local copy */ + + for ( ; ; ) { + tile = EDGETILE( dict, edge ); + if ( CROSSCHECK_CONTAINS( check, tile ) ) { + XP_Bool isBlank; + if ( rack_remove( engine, tile, &isBlank ) ) { + tiles[tileLength] = tile; + extendRight( engine, tiles, tileLength+1, + edge_from_tile( dict, edge, tile ), + ISACCEPTING( dict, edge ), firstCol, col+1, row ); + rack_replace( engine, tile, isBlank ); + if ( engine->returnNOW ) { + return; + } + } + } + + if ( IS_LAST_EDGE( dict, edge ) ) { + break; + } +#ifdef NODE_CAN_4 + edge += dict->nodeSize; +#else + edge += 3; +#endif + } + + } else if ( (edge = edge_with_tile( dict, edge, tile ) ) != NULL ) { + accepting = ISACCEPTING( dict, edge ); + extendRight( engine, tiles, tileLength, follow(dict, edge), + accepting, firstCol, col+1, row ); + return; /* don't do the check at the end */ + } else { + return; + } + check_exit: + if ( accepting ) { + considerMove( engine, tiles, tileLength, firstCol, col, row ); + } +} /* extendRight */ + +static XP_Bool +rack_remove( EngineCtxt* engine, Tile tile, XP_Bool* isBlank ) +{ + Tile blankIndex = engine->blankTile; + + XP_ASSERT( tile < 32 ); + XP_ASSERT( tile != blankIndex ); + + if ( engine->rack[(short)tile] > 0 ) { /* we have the tile itself */ + --engine->rack[(short)tile]; + *isBlank = XP_FALSE; + } else if ( engine->rack[blankIndex] > 0 ) { /* we have and must use a + blank */ + --engine->rack[(short)blankIndex]; + engine->blankValues[engine->blankCount++] = tile; + *isBlank = XP_TRUE; + } else { /* we can't satisfy the request */ + return XP_FALSE; + } + return XP_TRUE; +} /* rack_remove */ + +static void +rack_replace( EngineCtxt* engine, Tile tile, XP_Bool isBlank ) +{ + if ( isBlank ) { + --engine->blankCount; + tile = engine->blankTile; + } + ++engine->rack[(short)tile]; +} /* rack_replace */ + +static void +considerMove( EngineCtxt* engine, Tile* tiles, XP_S16 tileLength, + XP_S16 firstCol, XP_S16 lastCol, XP_S16 lastRow ) +{ + PossibleMove posmove; + short col; + BlankTuple blankTuples[MAX_NUM_BLANKS]; + + if ( !util_engineProgressCallback( engine->util ) ) { + engine->returnNOW = XP_TRUE; + return; + } + + /* if this never gets hit then the top-level caller of leftPart should + never pass a value greater than 7 for limit. I think we're always + guaranteed to run out of tiles before finding a legal move with larger + values but that it's expensive to look only to fail. */ + XP_ASSERT( engine->curLimit < MAX_TRAY_TILES ); + + XP_MEMSET( &posmove, 0, sizeof(posmove) ); + + for ( col = firstCol; posmove.moveInfo.nTiles < tileLength; ++col ) { + /* is it one of the new ones? */ + if ( localGetBoardTile( engine, col, lastRow, XP_FALSE ) + == EMPTY_TILE ) { + posmove.moveInfo.tiles[posmove.moveInfo.nTiles].tile = + tiles[posmove.moveInfo.nTiles]; + posmove.moveInfo.tiles[posmove.moveInfo.nTiles].varCoord = (XP_U8)col; + ++posmove.moveInfo.nTiles; + } + } + posmove.moveInfo.isHorizontal = engine->searchHorizontal; + posmove.moveInfo.commonCoord = (XP_U8)lastRow; + + + considerScoreWordHasBlanks( engine, engine->blankCount, &posmove, + lastRow, blankTuples, 0 ); +} /* considerMove */ + +static void +considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, + PossibleMove* posmove, + XP_U16 lastRow, BlankTuple* usedBlanks, + XP_U16 usedBlanksCount ) +{ + XP_U16 i; + + if ( blanksLeft == 0 ) { + XP_U16 score; + + score = figureMoveScore( engine->model, + &posmove->moveInfo, + engine, (XWStreamCtxt*)NULL, + XP_TRUE, (WordNotifierInfo*)NULL ); + + /* First, check that the score is even what we're interested in. If + it is, then go to the expense of filling in a PossibleMove to be + compared in full */ + if ( scoreQualifies( engine, score ) ) { + posmove->score = score; + XP_MEMSET( &posmove->blankVals, 0, sizeof(posmove->blankVals) ); + for ( i = 0; i < usedBlanksCount; ++i ) { + short col = usedBlanks[i].col; + posmove->blankVals[col] = usedBlanks[i].tile; + /* posmove->whichBlanks |= (1 << col); */ + } + XP_ASSERT( posmove->moveInfo.isHorizontal== + engine->searchHorizontal ); + /* posmove->moveInfo.isHorizontal = engine->searchHorizontal; */ + posmove->moveInfo.commonCoord = (XP_U8)lastRow; + saveMoveIfQualifies( engine, posmove ); + } + } else { + Tile bTile; + BlankTuple* bt; + + --blanksLeft; + XP_ASSERT( engine->blankValues[blanksLeft] < 128 ); + bTile = engine->blankValues[blanksLeft]; + bt = &usedBlanks[usedBlanksCount++]; + + /* for each letter for which the blank might be standing in... */ + for ( i = 0; i < posmove->moveInfo.nTiles; ++i ) { + CellTile tile = posmove->moveInfo.tiles[i].tile; + if ( (tile & TILE_VALUE_MASK) == bTile && !IS_BLANK(tile) ) { + posmove->moveInfo.tiles[i].tile |= TILE_BLANK_BIT; + bt->col = i; + bt->tile = bTile; + considerScoreWordHasBlanks( engine, blanksLeft, + posmove, lastRow, + usedBlanks, + usedBlanksCount ); + /* now put things back */ + posmove->moveInfo.tiles[i].tile &= ~TILE_BLANK_BIT; + } + } + } +} /* considerScoreWordHasBlanks */ + +static void +saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ) +{ + XP_S16 lowest = 0; + + if ( !engine->isRobot ) { /* robot doesn't ask for next hint.... */ + + /* we're not interested if we've seen this */ + if ( CMPMOVES( posmove, &engine->miData.lastSeenMove ) >= 0 ) { + lowest = -1; + } else { + XP_S16 i; + /* terminate i at 1 because lowest starts at 0 */ + for ( lowest = NUM_SAVED_MOVES-1, i = lowest - 1; i >= 0; --i ) { + /* Find the lowest value move and overwrite it. Note that + there might not be one, as all may have the same or higher + scores and those that have the same score may compare + higher. + + can't have this asssertion until I start noting the + lowest saved score (setting miData.lowestSavedScore) + below. */ + /* 1/20/2001 I don't see that this assertion is valid. I + simply don't understand why it isn't tripped all the time + in the old crosswords. */ + /* XP_ASSERT( (engine->miData.lastSeenMove.score == 0x7fff) */ + /* || (engine->miData.savedMoves[i].score */ + /* <= posmove->score) ); */ + + if ( CMPMOVES( &engine->miData.savedMoves[lowest], + &engine->miData.savedMoves[i] ) > 0 ) { + lowest = i; + } + } + } + } + if ( lowest >= 0) { + /* record the score we're dumping. No point in considering any scores + lower than this for the rest of this round. */ + engine->miData.lowestSavedScore = + engine->miData.savedMoves[lowest].score; + /* XP_DEBUGF( "lowestSavedScore now %d\n", */ + /* engine->miData.lowestSavedScore ); */ + if ( CMPMOVES( posmove, &engine->miData.savedMoves[lowest]) > 0 ) { + XP_MEMCPY( &engine->miData.savedMoves[lowest], posmove, + sizeof(engine->miData.savedMoves[lowest]) ); + /* XP_DEBUGF( "just saved move with score %d\n", */ + /* engine->miData.savedMoves[lowest].score ); */ + } + } +} /* saveMoveIfQualifies */ + +static XP_Bool +scoreQualifies( EngineCtxt* engine, XP_U16 score ) +{ + if ( (score > engine->miData.lastSeenMove.score) + || (score > engine->targetScore) + || (score < engine->miData.lowestSavedScore) ) { + return XP_FALSE; + } else { + XP_S16 i; + /* Look at each saved score, and return true as soon as one's found + with a lower score than this. As an optimization, consider + remembering what the lowest score is *once there are + NUM_SAVED_MOVES moves in here* and doing a quick test on that. */ + for ( i = engine->isRobot? 0: NUM_SAVED_MOVES-1; i >= 0; --i ) { + if ( score > engine->miData.savedMoves[i].score ) { + /* We could cache the value of i to know when to start + the saveMoveIfQualifies search that's upcomming. */ + return XP_TRUE; + } + } + return XP_FALSE; + } +} /* scoreQualifies */ + +static array_edge* +edge_with_tile( DictionaryCtxt* dict, array_edge* from, Tile tile ) +{ + for ( ; ; ) { + Tile candidate = EDGETILE(dict,from); + if ( candidate == tile ) { + break; + } + + if ( IS_LAST_EDGE(dict, from ) ) { + from = NULL; + break; + } +#ifdef NODE_CAN_4 + from += dict->nodeSize; +#else + from += 3; +#endif + + } + + return from; +} /* edge_with_tile */ + +static unsigned long +index_from( DictionaryCtxt* dict, array_edge* p_edge ) +{ + unsigned long result; + if ( 0 ) { +#ifdef NODE_CAN_4 + } else if ( dict->nodeSize == 4 ) { + array_edge_new* edge = (array_edge_new*)p_edge; + result = ((edge->o.highByte << 8) | edge->o.lowByte) & 0x0000FFFF; + result |= edge->moreBits << 17; + if ( (edge->o.bits & LASTBITMASK) != 0 ) { + result |= 0x00010000; /* using | instead of + saves 4 bytes */ + } +#endif + } else { + array_edge_old* edge = (array_edge_old*)p_edge; + result = ((edge->highByte << 8) | edge->lowByte) & 0x0000FFFF; + if ( (edge->bits & LASTBITMASK) != 0 ) { + result |= 0x00010000; /* using | instead of + saves 4 bytes */ + } + } + return result; +} /* index_from */ + +static array_edge* +follow( DictionaryCtxt* dict, array_edge* in ) +{ + XP_U32 index = index_from( dict, in ); + array_edge* result = index > 0? + dict_edge_for_index( dict, index ): (array_edge*)NULL; + return result; +} /* follow */ + +static array_edge* +edge_from_tile( DictionaryCtxt* dict, array_edge* from, Tile tile ) +{ + array_edge* edge = edge_with_tile( dict, from, tile ); + if ( edge != NULL ) { + edge = follow( dict, edge ); + } + return edge; +} /* edge_from_tile */ + +#ifdef CPLUS +} +#endif + diff --git a/xwords4/common/engine.h b/xwords4/common/engine.h new file mode 100644 index 000000000..0946729f6 --- /dev/null +++ b/xwords4/common/engine.h @@ -0,0 +1,61 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (fixin@peak.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. + */ + +#ifndef _ENGINE_H_ +#define _ENGINE_H_ + +#include "comtypes.h" +#include "dictnry.h" + +#ifdef CPLUS +extern "C" { +#endif + +XP_U16 engine_getScoreCache( EngineCtxt* engine, XP_U16 row ); + +#if 0 +#define engine_reset(e) /* nothing for now */ +#else + +typedef XP_Bool (*Continue_Callback)(void* state); + +EngineCtxt* engine_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isRobot ); + +void engine_writeToStream( EngineCtxt* ctxt, XWStreamCtxt* stream ); +EngineCtxt* engine_makeFromStream( MPFORMAL XWStreamCtxt* stream, + XW_UtilCtxt* util, XP_Bool isRobot ); + +void engine_init( EngineCtxt* ctxt ); +void engine_reset( EngineCtxt* ctxt ); +void engine_destroy( EngineCtxt* ctxt ); + +#define NO_SCORE_LIMIT 10000 /* for targetScore */ +XP_Bool engine_findMove( EngineCtxt* ctxt, ModelCtxt* model, + DictionaryCtxt* dict, const Tile* tiles, + XP_U16 numTiles, XP_U16 targetScore, XP_Bool* canMove, + MoveInfo* result ); +XP_Bool engine_check( DictionaryCtxt* dict, Tile* buf, XP_U16 buflen ); + +#endif /* 0 */ + +#ifdef CPLUS +} +#endif + +#endif /* _ENGINE_H_ */ diff --git a/xwords4/common/game.c b/xwords4/common/game.c new file mode 100644 index 000000000..ca631c133 --- /dev/null +++ b/xwords4/common/game.c @@ -0,0 +1,425 @@ +/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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 "game.h" +#include "dictnry.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +#ifdef DEBUG +static void +assertUtilOK( XW_UtilCtxt* util ) +{ + UtilVtable* vtable = util->vtable; + XP_U16 nSlots = sizeof(vtable) / 4; + while ( nSlots-- ) { + void* fptr = ((void**)vtable)[nSlots]; + XP_ASSERT( !!fptr ); + } +} /* assertUtilOK */ +#else +# define assertUtilOK(u) +#endif + +static void +checkServerRole( CurGameInfo* gi ) +{ + if ( !!gi ) { + + if ( gi->serverRole != SERVER_ISCLIENT ) { + XP_Bool standAlone = gi->serverRole == SERVER_STANDALONE; + XP_U16 i, remoteCount = 0; + + for ( i = 0; i < gi->nPlayers; ++i ) { + LocalPlayer* player = &gi->players[i]; + if ( !player->isLocal ) { + ++remoteCount; + if ( standAlone ) { + player->isLocal = XP_TRUE; + } + } + } + if ( remoteCount == 0 ) { + gi->serverRole = SERVER_STANDALONE; + } + } + } +} /* checkServerRole */ + +void +game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, + XW_UtilCtxt* util, DrawCtx* draw, + CommonPrefs* cp, + TransportSend sendproc, void* closure ) +{ + assertUtilOK( util ); + checkServerRole( gi ); + + game->model = model_make( MPPARM(mpool) (DictionaryCtxt*)NULL, util, + gi->boardSize, gi->boardSize ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!sendproc ) { + game->comms = comms_make( MPPARM(mpool) util, + gi->serverRole != SERVER_ISCLIENT, + sendproc, closure ); + } else { + game->comms = (CommsCtxt*)NULL; + } +#endif + game->server = server_make( MPPARM(mpool) game->model, +#ifndef XWFEATURE_STANDALONE_ONLY + game->comms, +#else + (CommsCtxt*)NULL, +#endif + util ); + game->board = board_make( MPPARM(mpool) game->model, game->server, + draw, util ); + + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); +} /* game_makeNewGame */ + +void +game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XP_U16 gameID, + CommonPrefs* cp, TransportSend sendproc, void* closure ) +{ + XP_U16 i; + + XP_ASSERT( !!game->model ); + XP_ASSERT( !!gi ); + + checkServerRole( gi ); + gi->gameID = gameID; + + model_init( game->model, gi->boardSize, gi->boardSize ); + server_reset( game->server ); + board_reset( game->board ); + + for ( i = 0; i < gi->nPlayers; ++i ) { + LocalPlayer* player = &gi->players[i]; + XP_Bool isLocal = player->isLocal; + if ( !isLocal ) { + player->name = (XP_UCHAR*)NULL; + } + player->secondsUsed = 0; + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!game->comms ) { + comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT ); + } +#endif + + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); +} /* game_reset */ + +void +game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game, + CurGameInfo* gi, DictionaryCtxt* dict, + XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp, + TransportSend sendProc, void* closure ) +{ +#ifndef XWFEATURE_STANDALONE_ONLY + XP_Bool hasComms; +#endif +#ifdef DEBUG + XP_U8 version = stream_getU8( stream ); + XP_ASSERT( version == CUR_STREAM_VERS ); +#else + (void)stream_getU8( stream ); +#endif + + gi_readFromStream( MPPARM(mpool) stream, gi ); + +#ifndef XWFEATURE_STANDALONE_ONLY + hasComms = stream_getU8( stream ); + game->comms = hasComms? + comms_makeFromStream( MPPARM(mpool) stream, util, sendProc, closure ): + (CommsCtxt*)NULL; +#endif + game->model = model_makeFromStream( MPPARM(mpool) stream, dict, util ); + + game->server = server_makeFromStream( MPPARM(mpool) stream, + game->model, +#ifndef XWFEATURE_STANDALONE_ONLY + game->comms, +#else + (CommsCtxt*)NULL, +#endif + util, gi->nPlayers ); + + game->board = board_makeFromStream( MPPARM(mpool) stream, game->model, + game->server, draw, util, + gi->nPlayers ); + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); +} /* game_makeFromStream */ + +void +game_saveToStream( XWGame* game, CurGameInfo* gi, XWStreamCtxt* stream ) +{ + stream_putU8( stream, CUR_STREAM_VERS ); + + gi_writeToStream( stream, gi ); + +#ifndef XWFEATURE_STANDALONE_ONLY + stream_putU8( stream, (XP_U8)!!game->comms ); + if ( !!game->comms ) { + comms_writeToStream( game->comms, stream ); + } +#endif + + model_writeToStream( game->model, stream ); + server_writeToStream( game->server, stream ); + board_writeToStream( game->board, stream ); +} /* game_saveToStream */ + +void +game_dispose( XWGame* game ) +{ + /* The board should be reused!!! PENDING(ehouse) */ + if ( !!game->board ) { + board_destroy( game->board ); + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!game->comms ) { + comms_destroy( game->comms ); + } +#endif + if ( !!game->model ) { + DictionaryCtxt* dict = model_getDictionary( game->model ); + if ( !!dict ) { + dict_destroy( dict ); + } + model_destroy( game->model ); + } + if ( !!game->server ) { + server_destroy( game->server ); + } +} /* game_dispose */ + +void +gi_initPlayerInfo( MPFORMAL CurGameInfo* gi, XP_UCHAR* nameTemplate ) +{ + XP_U16 i; + + XP_MEMSET( gi, 0, sizeof(*gi) ); + gi->nPlayers = 2; + gi->boardSize = 15; + gi->robotSmartness = SMART_ROBOT; + gi->timerEnabled = XP_FALSE; + gi->gameSeconds = 25 * 60; /* 25 minute game is common? */ + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + XP_UCHAR buf[20]; + LocalPlayer* fp = &gi->players[i]; + + if ( !!nameTemplate ) { + XP_SNPRINTF( buf, sizeof(buf), nameTemplate, i+1 ); + XP_ASSERT( fp->name == NULL ); + fp->name = copyString( MPPARM(mpool) buf ); + } + + fp->isRobot = (i == 0); /* one robot */ + fp->isLocal = XP_TRUE; + fp->secondsUsed = 0; + } +} /* game_initPlayerInfo */ + +static void +disposePlayerInfoInt( MPFORMAL CurGameInfo* gi, XP_Bool namesToo ) +{ + XP_U16 i; + LocalPlayer* lp; + + for ( lp = gi->players, i = 0; i < MAX_NUM_PLAYERS; ++lp, ++i ) { + if ( !!lp->name ) { + XP_FREE( mpool, lp->name ); + lp->name = (XP_UCHAR*)NULL; + } + if ( !!lp->password ) { + XP_FREE( mpool, lp->password ); + lp->password = (XP_UCHAR*)NULL; + } + } +} /* disposePlayerInfoInt */ + +void +gi_disposePlayerInfo( MPFORMAL CurGameInfo* gi ) +{ + disposePlayerInfoInt( MPPARM(mpool) gi, XP_TRUE ); + + if ( !!gi->dictName ) { + XP_FREE( mpool, gi->dictName ); + gi->dictName = (XP_UCHAR*)NULL; + } +} /* gi_disposePlayerInfo */ + +void +gi_copy( MPFORMAL CurGameInfo* destGI, CurGameInfo* srcGI ) +{ + XP_U16 nPlayers, i; + LocalPlayer* srcPl; + LocalPlayer* destPl; + + replaceStringIfDifferent( MPPARM(mpool) &destGI->dictName, + srcGI->dictName ); + + destGI->gameID = srcGI->gameID; + destGI->gameSeconds = srcGI->gameSeconds; + destGI->nPlayers = (XP_U8)srcGI->nPlayers; + nPlayers = srcGI->nPlayers; + destGI->boardSize = (XP_U8)srcGI->boardSize; + destGI->serverRole = srcGI->serverRole; + + destGI->hintsNotAllowed = srcGI->hintsNotAllowed; + destGI->timerEnabled = srcGI->timerEnabled; + destGI->robotSmartness = (XP_U8)srcGI->robotSmartness; + destGI->phoniesAction = srcGI->phoniesAction; + + for ( srcPl = srcGI->players, destPl = destGI->players, i = 0; + i < nPlayers; ++srcPl, ++destPl, ++i ) { + + replaceStringIfDifferent( MPPARM(mpool) &destPl->name, srcPl->name ); + replaceStringIfDifferent( MPPARM(mpool) &destPl->password, + srcPl->password ); + destPl->secondsUsed = srcPl->secondsUsed; + destPl->isRobot = srcPl->isRobot; + destPl->isLocal = srcPl->isLocal; + } +} /* gi_copy */ + +void +gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ) +{ + LocalPlayer* pl; + XP_U16 i; + XP_UCHAR* str; + + str = stringFromStream( MPPARM(mpool) stream ); + replaceStringIfDifferent( MPPARM(mpool) &gi->dictName, str ); + if ( !!str ) { + XP_FREE( mpool, str ); + } + + gi->nPlayers = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); + gi->boardSize = (XP_U8)stream_getBits( stream, 4 ); + gi->serverRole = (Connectedness)stream_getBits( stream, 2 ); + gi->hintsNotAllowed = stream_getBits( stream, 1 ); + gi->robotSmartness = (XP_U8)stream_getBits( stream, 2 ); + gi->phoniesAction = (XWPhoniesChoice)stream_getBits( stream, 2 ); + gi->timerEnabled = stream_getBits( stream, 1 ); + + gi->gameID = stream_getU16( stream ); + if ( gi->timerEnabled ) { + gi->gameSeconds = stream_getU16( stream ); + } + + for ( pl = gi->players, i = 0; i < gi->nPlayers; ++pl, ++i ) { + str = stringFromStream( MPPARM(mpool) stream ); + replaceStringIfDifferent( MPPARM(mpool) &pl->name, str ); + if ( !!str ) { + XP_FREE( mpool, str ); + } + + str = stringFromStream( MPPARM(mpool) stream ); + replaceStringIfDifferent( MPPARM(mpool) &pl->password, str ); + if ( !!str ) { + XP_FREE( mpool, str ); + } + + pl->secondsUsed = stream_getU16( stream ); + pl->isRobot = stream_getBits( stream, 1 ); + pl->isLocal = stream_getBits( stream, 1 ); + } +} /* gi_readFromStream */ + +void +gi_writeToStream( XWStreamCtxt* stream, CurGameInfo* gi ) +{ + LocalPlayer* pl; + XP_U16 i; + + stringToStream( stream, gi->dictName ); + + stream_putBits( stream, NPLAYERS_NBITS, gi->nPlayers ); + stream_putBits( stream, 4, gi->boardSize ); + stream_putBits( stream, 2, gi->serverRole ); + stream_putBits( stream, 1, gi->hintsNotAllowed ); + stream_putBits( stream, 2, gi->robotSmartness ); + stream_putBits( stream, 2, gi->phoniesAction ); + stream_putBits( stream, 1, gi->timerEnabled ); + + stream_putU16( stream, gi->gameID ); + if ( gi->timerEnabled) { + stream_putU16( stream, gi->gameSeconds ); + } + + for ( pl = gi->players, i = 0; i < gi->nPlayers; ++pl, ++i ) { + stringToStream( stream, pl->name ); + stringToStream( stream, pl->password ); + stream_putU16( stream, pl->secondsUsed ); + stream_putBits( stream, 1, pl->isRobot ); + stream_putBits( stream, 1, pl->isLocal ); + } +} /* gi_writeToStream */ + +XP_Bool +player_hasPasswd( LocalPlayer* player ) +{ + XP_UCHAR* password = player->password; + /* XP_ASSERT( player->isLocal ); */ + return !!password && *password != '\0'; +} /* player_hasPasswd */ + +XP_Bool +player_passwordMatches( LocalPlayer* player, XP_U8* buf, XP_U16 len ) +{ + XP_ASSERT( player->isLocal ); + + return (XP_STRLEN(player->password) == len) + && (0 == XP_STRNCMP( player->password, (XP_UCHAR*)buf, len )); +} /* player_passwordMatches */ + +XP_U16 +player_timePenalty( CurGameInfo* gi, XP_U16 playerNum ) +{ + XP_S16 seconds = (gi->gameSeconds / gi->nPlayers); + LocalPlayer* player = gi->players + playerNum; + XP_U16 result = 0; + + seconds -= player->secondsUsed; + if ( seconds < 0 ) { + seconds = -seconds; + seconds += 59; + result = (seconds/60) * 10; + } + return result; +} /* player_timePenalty */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/game.h b/xwords4/common/game.h new file mode 100644 index 000000000..6a9b7630f --- /dev/null +++ b/xwords4/common/game.h @@ -0,0 +1,99 @@ +/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _GAME_H_ +#define _GAME_H_ + +#include "model.h" +#include "board.h" +#include "comms.h" +#include "server.h" +#include "util.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define CUR_STREAM_VERS 0x01 + +typedef struct LocalPlayer { + XP_UCHAR* name; + XP_UCHAR* password; + XP_U16 secondsUsed; + XP_Bool isRobot; + XP_Bool isLocal; +} LocalPlayer; + +#define DUMB_ROBOT 0 +#define SMART_ROBOT 1 + +typedef struct CurGameInfo { + XP_UCHAR* dictName; + LocalPlayer players[MAX_NUM_PLAYERS]; + XP_U16 gameID; /* uniquely identifies game */ + XP_U16 gameSeconds; /* for timer */ + XP_U8 nPlayers; + XP_U8 boardSize; + Connectedness serverRole; + + XP_Bool hintsNotAllowed; + XP_Bool timerEnabled; + XP_U8 robotSmartness; + XWPhoniesChoice phoniesAction; + +} CurGameInfo; + +typedef struct XWGame { + BoardCtxt* board; + ModelCtxt* model; + ServerCtxt* server; +#ifndef XWFEATURE_STANDALONE_ONLY + CommsCtxt* comms; +#endif +} XWGame; + +void game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, + XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp, + TransportSend sendproc, void* closure ); +void game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XP_U16 gameID, + CommonPrefs* cp, TransportSend sendproc, void* closure ); + +void game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game, + CurGameInfo* gi, + DictionaryCtxt* dict, XW_UtilCtxt* util, + DrawCtx* draw, CommonPrefs* cp, + TransportSend sendProc, void* closure ); + +void game_saveToStream( XWGame* game, CurGameInfo* gi, XWStreamCtxt* stream ); +void game_dispose( XWGame* game ); +void gi_initPlayerInfo( MPFORMAL CurGameInfo* gi, XP_UCHAR* nameTemplate ); +void gi_disposePlayerInfo( MPFORMAL CurGameInfo* gi ); +void gi_writeToStream( XWStreamCtxt* stream, CurGameInfo* gi ); +void gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ); +void gi_copy( MPFORMAL CurGameInfo* destGI, CurGameInfo* srcGi ); + +XP_Bool player_hasPasswd( LocalPlayer* player ); +XP_Bool player_passwordMatches( LocalPlayer* player, XP_U8* buf, XP_U16 len ); +XP_U16 player_timePenalty( CurGameInfo* gi, XP_U16 playerNum ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c new file mode 100644 index 000000000..fad34532a --- /dev/null +++ b/xwords4/common/mempool.c @@ -0,0 +1,244 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifdef MEM_DEBUG + +#include "mempool.h" +#include "comtypes.h" +#include "xwstream.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct MemPoolEntry { + struct MemPoolEntry* next; + char* fileName; + XP_U32 lineNo; + XP_U32 size; + void* ptr; +} MemPoolEntry; + +struct MemPoolCtx { + MemPoolEntry* freeList; + MemPoolEntry* usedList; + + XP_U16 nFree; + XP_U16 nUsed; + XP_U16 nAllocs; +}; + +/*--------------------------------------------------------------------------*/ + +MemPoolCtx* +mpool_make() +{ + MemPoolCtx* result = (MemPoolCtx*)XP_PLATMALLOC( sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + return result; +} /* mpool_make */ + +static void +freeList( MemPoolEntry* entry ) +{ + while ( !!entry ) { + MemPoolEntry* next = entry->next; + + XP_ASSERT( !entry->ptr ); + XP_PLATFREE( entry ); + + entry = next; + } +} /* freeList */ + +#ifdef DEBUG +static char* +checkIsText( MemPoolEntry* entry ) +{ + unsigned char* txt = (unsigned char*)entry->ptr; + XP_U32 len = entry->size; + + while ( len-- ) { + unsigned char c = *txt++; + if ( c < 32 || c > 127 ) { + if ( len == 0 && c == '\0' ) { + return (char*)entry->ptr; + } else { + return (char*)NULL; + } + } + } + + return (char*)NULL; +} /* checkIsText */ +#endif + +void +mpool_destroy( MemPoolCtx* mpool ) +{ + if ( !!mpool->usedList ) { + MemPoolEntry* entry; + for ( entry = mpool->usedList; !!entry; entry = entry->next ) { +#ifndef FOR_GREMLINS /* I don't want to hear about this right now */ + XP_WARNF( "0x%lx from ln %ld of %s\n", + entry->ptr, entry->lineNo, entry->fileName ); +#ifdef DEBUG + { + char* tryTxt; + tryTxt = checkIsText( entry ); + if ( !!tryTxt ) { + XP_WARNF( "--- looks like text: %s\n", tryTxt ); + } + } +#endif +#endif + } + } + +#ifndef FOR_GREMLINS + XP_ASSERT( !mpool->usedList && mpool->nUsed == 0 ); +#endif + + freeList( mpool->freeList ); + XP_PLATFREE( mpool ); +} /* mpool_destroy */ + +void* +mpool_alloc( MemPoolCtx* mpool, XP_U32 size, char* file, XP_U32 lineNo ) +{ + MemPoolEntry* entry; + + if ( mpool->nFree > 0 ) { + entry = mpool->freeList; + mpool->freeList = entry->next; + --mpool->nFree; + } else { + entry = (MemPoolEntry*)XP_PLATMALLOC( sizeof(*entry) ); + } + + entry->next = mpool->usedList; + mpool->usedList = entry; + + entry->fileName = file; + entry->lineNo = lineNo; + entry->size = size; + entry->ptr = XP_PLATMALLOC( size ); + XP_ASSERT( !!entry->ptr ); + + ++mpool->nUsed; + ++mpool->nAllocs; + + return entry->ptr; +} /* mpool_alloc */ + +static MemPoolEntry* +findEntryFor( MemPoolCtx* mpool, void* ptr, MemPoolEntry** prevP ) +{ + MemPoolEntry* entry; + MemPoolEntry* prev; + + for ( prev = (MemPoolEntry*)NULL, entry = mpool->usedList; !!entry; + prev = entry, entry = prev->next ) { + + if ( entry->ptr == ptr ) { + + if ( !!prevP ) { + *prevP = prev; + } + + return entry; + } + } + XP_ASSERT(0); + return (MemPoolEntry*)NULL; +} /* findEntryFor */ + +void* +mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize ) +{ + MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL ); + + entry->ptr = XP_PLATREALLOC( entry->ptr, newsize ); + XP_ASSERT( !!entry->ptr ); + return entry->ptr; +} /* mpool_realloc */ + +void +mpool_free( MemPoolCtx* mpool, void* ptr, char* file, XP_U32 lineNo ) +{ + MemPoolEntry* entry; + MemPoolEntry* prev; + + entry = findEntryFor( mpool, ptr, &prev ); + + if ( !!entry ) { + + if ( !!prev ) { + prev->next = entry->next; + } else { + mpool->usedList = entry->next; + } + + XP_MEMSET( entry->ptr, 0x00, entry->size ); + XP_PLATFREE( entry->ptr ); + entry->ptr = NULL; + + entry->next = mpool->freeList; + mpool->freeList = entry; + + ++mpool->nFree; + --mpool->nUsed; + + return; + } + + XP_ASSERT( 0 ); +} /* mpool_free */ + +void +mpool_stats( MemPoolCtx* mpool, XWStreamCtxt* stream ) +{ + XP_UCHAR buf[128]; + MemPoolEntry* entry; + + XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"Number of blocks in use: %d\n" + "Number of free blocks: %d\n" + "Total number of blocks allocated: %d\n", + mpool->nUsed, mpool->nFree, mpool->nAllocs ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN(buf) ); + + for ( entry = mpool->usedList; !!entry; entry = entry->next ) { + XP_SNPRINTF( buf, sizeof(buf), + (XP_UCHAR*)"%ld byte block allocated %s: line %ld\n", + entry->size, entry->fileName, entry->lineNo ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN(buf) ); + } +} /* mpool_stats */ + +XP_U16 +mpool_getNUsed( MemPoolCtx* mpool ) +{ + return mpool->nUsed; +} /* mpool_getNUsed */ + +#ifdef CPLUS +} +#endif + +#endif /* MEM_DEBUG */ diff --git a/xwords4/common/mempool.h b/xwords4/common/mempool.h new file mode 100644 index 000000000..2ac07450c --- /dev/null +++ b/xwords4/common/mempool.h @@ -0,0 +1,52 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _MEMPOOL_H_ +#define _MEMPOOL_H_ + +#ifdef MEM_DEBUG + +#include "comtypes.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct MemPoolCtx MemPoolCtx; + +MemPoolCtx* mpool_make(); +void mpool_destroy( MemPoolCtx* mpool ); + +void* mpool_alloc( MemPoolCtx* mpool, XP_U32 size, char* file, + XP_U32 lineNo ); +void* mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize ); +void mpool_free( MemPoolCtx* mpool, void* ptr, char* file, XP_U32 lineNo ); +void mpool_stats( MemPoolCtx* mpool, XWStreamCtxt* stream ); +XP_U16 mpool_getNUsed( MemPoolCtx* mpool ); + +#ifdef CPLUS +} +#endif + +#else + +# define mpool_destroy(p) + +#endif /* MEM_DEBUG */ +#endif /* _MEMPOOL_H_ */ diff --git a/xwords4/common/memstream.c b/xwords4/common/memstream.c new file mode 100644 index 000000000..d9c7b3ff9 --- /dev/null +++ b/xwords4/common/memstream.c @@ -0,0 +1,416 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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 */ +/* #include */ +/* #include */ + +#include "xwstream.h" +#include "comtypes.h" +#include "memstream.h" +#include "vtabmgr.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define BIT_PART(pos) ((pos)&0x00000007) +#define BYTE_PART(pos) ((pos)>>3) + +#define MIN_PACKETBUF_SIZE (1<<6) + +#define STREAM_INCR_SIZE 100 + +#define SOCKET_STREAM_SUPER_COMMON_SLOTS \ + StreamCtxVTable* vtable; \ + void* closure; \ + XP_U32 curReadPos; \ + XP_U32 curWritePos; \ + XP_PlayerAddr channelNo; \ + XP_U8* buf; \ + MemStreamCloseCallback onClose; \ + XP_U16 nBytesWritten; \ + XP_U16 nBytesAllocated; \ + XP_U8 nReadBits; \ + XP_U8 nWriteBits; \ + XP_Bool isOpen; \ + MPSLOT + +#define SOCKET_STREAM_SUPER_SLOTS \ + SOCKET_STREAM_SUPER_COMMON_SLOTS + +typedef struct MemStreamCtxt { + SOCKET_STREAM_SUPER_SLOTS +} MemStreamCtxt; + +static StreamCtxVTable* make_vtable( MemStreamCtxt* stream ); + +/* Try to keep this the only entry point to this file, and to keep it at the + * top of the file (first executable code). + */ +XWStreamCtxt* +mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure, + XP_PlayerAddr channelNo, MemStreamCloseCallback onClose ) +{ + StreamCtxVTable* vtable; + MemStreamCtxt* result = (MemStreamCtxt*)XP_MALLOC( mpool, + sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + vtable = (StreamCtxVTable*)vtmgr_getVTable( vtmgr, VTABLE_MEM_STREAM ); + if ( !vtable ) { + vtable = make_vtable( result ); + vtmgr_setVTable( vtmgr, VTABLE_MEM_STREAM, vtable ); + } + result->vtable = vtable; + + result->closure = closure; + result->channelNo = channelNo; + result->onClose = onClose; + + result->isOpen = XP_TRUE; + + return (XWStreamCtxt*)result; +} /* make_mem_stream */ + +static void +mem_stream_getBytes( XWStreamCtxt* p_sctx, void* where, XP_U16 count ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + if ( stream->nReadBits != 0 ) { + stream->nReadBits = 0; + } + + XP_ASSERT( stream->curReadPos + count <= stream->nBytesAllocated ); + XP_ASSERT( stream->curReadPos + count <= stream->nBytesWritten ); + + XP_MEMCPY( where, stream->buf + stream->curReadPos, count ); + stream->curReadPos += count; + XP_ASSERT( stream->curReadPos <= stream->nBytesWritten ); +} /* mem_stream_getBytes */ + +static XP_U8 +mem_stream_getU8( XWStreamCtxt* p_sctx ) +{ + XP_U8 result; + mem_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* mem_stream_getU8 */ + +static XP_U16 +mem_stream_getU16( XWStreamCtxt* p_sctx ) +{ + XP_U16 result; + mem_stream_getBytes( p_sctx, &result, sizeof(result) ); + + return XP_NTOHS(result); +} /* mem_stream_getU16 */ + +static XP_U32 +mem_stream_getU32( XWStreamCtxt* p_sctx ) +{ + XP_U32 result; + mem_stream_getBytes( p_sctx, &result, sizeof(result) ); + return XP_NTOHL( result ); +} /* mem_stream_getU32 */ + +static XP_Bool +getOneBit( MemStreamCtxt* stream ) +{ + XP_U8 mask, rack; + XP_Bool result; + + if ( stream->nReadBits == 0 ) { + ++stream->curReadPos; + } + + rack = stream->buf[stream->curReadPos-1]; + mask = 1 << stream->nReadBits++; + result = (rack & mask) != 0; + + if ( stream->nReadBits == 8 ) { + stream->nReadBits = 0; + } + return result; +} /* getOneBit */ + +static XP_U32 +mem_stream_getBits( XWStreamCtxt* p_sctx, XP_U16 nBits ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XP_U32 mask; + XP_U32 result = 0; + + for ( mask = 1L; nBits--; mask <<= 1 ) { + if ( getOneBit( stream ) ) { + result |= mask; + } + } + + return result; +} /* stream_getBits */ + +static void +mem_stream_putBytes( XWStreamCtxt* p_sctx, void* whence, XP_U16 count ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XP_U32 newSize; + + if ( !stream->buf ) { + XP_ASSERT( stream->nBytesAllocated == 0 ); + stream->buf = (XP_U8*)XP_MALLOC( stream->mpool, STREAM_INCR_SIZE ); + stream->nBytesAllocated = STREAM_INCR_SIZE; + } + + /* I don't yet deal with getting asked to get/put a byte when in the + middle of doing bitwise stuff. It's probably just a matter of skipping + to the next byte, though -- and curPos should already be there. */ + if ( stream->nWriteBits != 0 ) { + stream->nWriteBits = 0; + } + + /* Reallocation. We may be writing into the middle of an existing stream, + and doing so may still require expanding the stream. So figure out if + the new size is bigger than what we have, and if so expand to hold it + plus something. */ + + newSize = stream->nBytesWritten + count; + if ( stream->curWritePos < stream->nBytesWritten ) { + newSize -= stream->nBytesWritten - stream->curWritePos; + } + + if ( newSize > stream->nBytesAllocated ) { + XP_ASSERT( newSize + STREAM_INCR_SIZE < 0xFFFF ); + stream->nBytesAllocated = (XP_U16)newSize + STREAM_INCR_SIZE; + stream->buf = + (XP_U8*)XP_REALLOC( stream->mpool, stream->buf, + stream->nBytesAllocated ); + } + + XP_MEMCPY( stream->buf + stream->curWritePos, whence, count ); + stream->nBytesWritten = (XP_U16)newSize; + stream->curWritePos += count; +} /* mem_stream_putBytes */ + +static void +mem_stream_putU8( XWStreamCtxt* p_sctx, XP_U8 data ) +{ + mem_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* mem_stream_putU8 */ + +static void +mem_stream_putU16( XWStreamCtxt* p_sctx, XP_U16 data ) +{ + data = XP_HTONS( data ); + mem_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_common_stream_putU16 */ + +static void +mem_stream_putU32( XWStreamCtxt* p_sctx, XP_U32 data ) +{ + data = XP_HTONL( data ); + mem_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* mem_stream_putU32 */ + +static void +putOneBit( MemStreamCtxt* stream, XP_U16 bit ) +{ + XP_U8 mask, rack; + + if ( stream->nWriteBits == 0 ) { + if ( stream->curWritePos == stream->nBytesWritten ) { + stream_putU8( (XWStreamCtxt*)stream, 0 ); /* increments curPos */ + } else { + ++stream->curWritePos; + } + } + + XP_ASSERT( stream->curWritePos > 0 ); + rack = stream->buf[stream->curWritePos-1]; + mask = 1 << stream->nWriteBits++; + if ( bit ) { + rack |= mask; + } else { + rack &= ~mask; + } + stream->buf[stream->curWritePos-1] = rack; + + stream->nWriteBits %= 8; +} /* putOneBit */ + +static void +mem_stream_putBits( XWStreamCtxt* p_sctx, XP_U16 nBits, XP_U32 data ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + XP_ASSERT( nBits > 0 ); + + while ( nBits-- ) { + putOneBit( stream, (XP_U16)(((data & 1L) != 0)? 1:0) ); + data >>= 1; + } + XP_ASSERT( data == 0 ); /* otherwise nBits was too smalls */ +} /* mem_stream_putBits */ + +static void +mem_stream_copyFromStream( XWStreamCtxt* p_sctx, XWStreamCtxt* src, + XP_U16 nBytes ) +{ + while ( nBytes-- ) { + XP_U8 byt = stream_getU8( src ); + stream_putU8( p_sctx, byt ); + } +} /* mem_stream_copyFromStream */ + +static void +mem_stream_open( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + stream->nBytesWritten = 0; + stream->curReadPos = START_OF_STREAM; + stream->curWritePos = START_OF_STREAM; +} /* mem_stream_open */ + +static void +mem_stream_close( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + XP_ASSERT( stream->isOpen ); + + if ( !!stream->onClose ) { + (*stream->onClose)( p_sctx, stream->closure ); + } + stream->isOpen = XP_FALSE; +} /* mem_stream_close */ + +static XP_U16 +mem_stream_getSize( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + return stream->nBytesWritten; +} /* mem_stream_getSize */ + +static XP_PlayerAddr +mem_stream_getAddress( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + return stream->channelNo; +} /* mem_stream_getAddress */ + +static void +mem_stream_setAddress( XWStreamCtxt* p_sctx, XP_PlayerAddr channelNo ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + stream->channelNo = channelNo; +} /* mem_stream_getAddress */ + +static XWStreamPos +mem_stream_getPos( XWStreamCtxt* p_sctx, PosWhich which ) +{ + XWStreamPos result; + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + if ( which == POS_WRITE ) { + result = (stream->curWritePos << 3) | stream->nWriteBits; + } else { + result = (stream->curReadPos << 3) | stream->nReadBits; + } + + return result; +} /* mem_stream_getPos */ + +static XWStreamPos +mem_stream_setPos( XWStreamCtxt* p_sctx, XWStreamPos newpos, PosWhich which ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XWStreamPos oldPos = mem_stream_getPos( p_sctx, which ); + + if ( which == POS_WRITE ) { + stream->nWriteBits = (XP_U8)BIT_PART(newpos); + stream->curWritePos = (XP_U32)BYTE_PART(newpos); + } else { + stream->nReadBits = (XP_U8)BIT_PART(newpos); + stream->curReadPos = (XP_U32)BYTE_PART(newpos); + } + + return oldPos; +} /* mem_stream_setPos */ + +static void +mem_stream_destroy( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + if ( stream->isOpen ) { + stream_close( p_sctx ); + } + + if ( !!stream->buf ) { + XP_FREE( stream->mpool, stream->buf ); + } + + XP_FREE( stream->mpool, stream ); +} /* mem_stream_destroy */ + +static StreamCtxVTable* +make_vtable( MemStreamCtxt* stream ) +{ + StreamCtxVTable* vtable; + XP_ASSERT( !stream->vtable ); + XP_ASSERT( sizeof(stream->vtable) == sizeof(vtable) ); + vtable = (StreamCtxVTable*)XP_MALLOC( stream->mpool, + sizeof(*stream->vtable) ); + + SET_VTABLE_ENTRY( vtable, stream_getU8, mem ); + SET_VTABLE_ENTRY( vtable, stream_getBytes, mem ); + SET_VTABLE_ENTRY( vtable, stream_getU16, mem ); + SET_VTABLE_ENTRY( vtable, stream_getU32, mem ); + SET_VTABLE_ENTRY( vtable, stream_getBits, mem ); + + SET_VTABLE_ENTRY( vtable, stream_putU8, mem ); + SET_VTABLE_ENTRY( vtable, stream_putBytes, mem ); + SET_VTABLE_ENTRY( vtable, stream_putU16, mem ); + SET_VTABLE_ENTRY( vtable, stream_putU32, mem ); + SET_VTABLE_ENTRY( vtable, stream_putBits, mem ); + + SET_VTABLE_ENTRY( vtable, stream_copyFromStream, mem ); + + SET_VTABLE_ENTRY( vtable, stream_setPos, mem ); + SET_VTABLE_ENTRY( vtable, stream_getPos, mem ); + + SET_VTABLE_ENTRY( vtable, stream_destroy, mem ); + SET_VTABLE_ENTRY( vtable, stream_open, mem ); + SET_VTABLE_ENTRY( vtable, stream_close, mem ); + + SET_VTABLE_ENTRY( vtable, stream_getSize, mem ); + SET_VTABLE_ENTRY( vtable, stream_getAddress, mem ); + SET_VTABLE_ENTRY( vtable, stream_setAddress, mem ); + + return vtable; +} /* make_vtable */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/memstream.h b/xwords4/common/memstream.h new file mode 100644 index 000000000..ebf34115c --- /dev/null +++ b/xwords4/common/memstream.h @@ -0,0 +1,46 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _MEMSTREAM_H_ +#define _MEMSTREAM_H_ + + +#include "comtypes.h" +#include "mempool.h" +#include "vtabmgr.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef void (*MemStreamCloseCallback)( XWStreamCtxt* stream, + void* closure ); + +XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr, + void* closure, + XP_PlayerAddr addr, /* should be in a + subclass */ + MemStreamCloseCallback onCloseWritten ); + + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/model.c b/xwords4/common/model.c new file mode 100644 index 000000000..cf891a89e --- /dev/null +++ b/xwords4/common/model.c @@ -0,0 +1,1488 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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 */ + +#include "comtypes.h" +#include "modelp.h" +#include "xwstream.h" +#include "util.h" +#include "pool.h" +#include "strutils.h" +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define mEND 0x6d454e44 + +/****************************** prototypes ******************************/ +typedef void (*MovePrintFuncPre)(ModelCtxt*, XP_U16, StackEntry*, void*); +typedef void (*MovePrintFuncPost)(ModelCtxt*, XP_U16, StackEntry*, XP_S16, + void*); + +static void incrPendingTileCountAt( ModelCtxt* model, XP_U16 col, + XP_U16 row ); +static void decrPendingTileCountAt( ModelCtxt* model, XP_U16 col, + XP_U16 row ); +static void notifyBoardListeners( ModelCtxt* model, XP_U16 turn, + XP_U16 col, XP_U16 row, XP_Bool added ); +static void notifyTrayListeners( ModelCtxt* model, XP_U16 turn, TileBit bits); +static CellTile getModelTileRaw( ModelCtxt* model, XP_U16 col, XP_U16 row ); +static void setModelTileRaw( ModelCtxt* model, XP_U16 col, XP_U16 row, + CellTile tile ); +static void assignPlayerTiles( ModelCtxt* model, XP_S16 turn, + TrayTileSet* tiles ); +static void makeTileTrade( ModelCtxt* model, XP_S16 player, + TrayTileSet* oldTiles, TrayTileSet* newTiles ); +static XP_S16 commitTurn( ModelCtxt* model, XP_S16 turn, + TrayTileSet* newTiles, XWStreamCtxt* stream, + XP_Bool useStack ); +static void buildModelFromStack( ModelCtxt* model, StackCtxt* stack, + XWStreamCtxt* stream, + MovePrintFuncPre mpfpr, + MovePrintFuncPost mpfpo, + void* closure ); +static void setPendingCounts( ModelCtxt* model, XP_S16 turn ); +static void loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); +static void writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); + + +/***************************************************************************** + * + ****************************************************************************/ +ModelCtxt* +model_make( MPFORMAL DictionaryCtxt* dict, XW_UtilCtxt* util, XP_U16 nCols, + XP_U16 nRows ) +{ + ModelCtxt* result = (ModelCtxt*)XP_MALLOC( mpool, sizeof( *result ) ); + if ( result != NULL ) { + XP_MEMSET( result, 0, sizeof(*result) ); + MPASSIGN(result->vol.mpool, mpool); + + result->vol.dict = dict; + result->vol.util = util; + + model_init( result, nCols, nRows ); + + XP_ASSERT( !!util->gameInfo ); + result->vol.gi = util->gameInfo; + } + + return result; +} /* model_make */ + +ModelCtxt* +model_makeFromStream( MPFORMAL XWStreamCtxt* stream, DictionaryCtxt* dict, + XW_UtilCtxt* util ) +{ + ModelCtxt* model; + DictionaryCtxt* savedDict = (DictionaryCtxt*)NULL; + XP_U16 nCols, nRows; + short i; + XP_Bool hasDict; + XP_U16 nPlayers; + + nCols = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + nRows = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + + hasDict = stream_getBits( stream, 1 ); + nPlayers = (XP_U16)stream_getBits( stream, NPLAYERS_NBITS ); + + if ( hasDict ) { + savedDict = util_makeEmptyDict( util ); + dict_loadFromStream( savedDict, stream ); + + if ( !!dict ) { + XP_ASSERT( dict_tilesAreSame( savedDict, dict ) ); + dict_destroy( savedDict ); + savedDict = dict; + } + } + + model = model_make( MPPARM(mpool) savedDict, util, nCols, nRows ); + model->nPlayers = nPlayers; + + stack_loadFromStream( model->vol.stack, stream ); + + buildModelFromStack( model, model->vol.stack, (XWStreamCtxt*)NULL, + (MovePrintFuncPre)NULL, + (MovePrintFuncPost)NULL, NULL ); + + for ( i = 0; i < model->nPlayers; ++i ) { + loadPlayerCtxt( stream, &model->players[i] ); + setPendingCounts( model, i ); + invalidateScore( model, i ); + } + + XP_ASSERT( stream_getU32( stream ) == mEND ); + + return model; +} /* model_makeFromStream */ + +void +model_writeToStream( ModelCtxt* model, XWStreamCtxt* stream ) +{ + short i; + DictionaryCtxt* dict; + + stream_putBits( stream, NUMCOLS_NBITS, model->nCols ); + stream_putBits( stream, NUMCOLS_NBITS, model->nRows ); + + dict = model_getDictionary( model ); + stream_putBits( stream, 1, dict != NULL ); + /* we have two bits for nPlayers, so range must be 0..3, not 1..4 */ + stream_putBits( stream, NPLAYERS_NBITS, model->nPlayers ); + + if ( dict != NULL ) { + dict_writeToStream( model_getDictionary( model ), stream ); + } + + stack_writeToStream( model->vol.stack, stream ); + + for ( i = 0; i < model->nPlayers; ++i ) { + writePlayerCtxt( stream, &model->players[i] ); + } + +#ifdef DEBUG + stream_putU32( stream, mEND ); +#endif +} /* model_writeToStream */ + +void +model_init( ModelCtxt* model, XP_U16 nCols, XP_U16 nRows ) +{ + ModelVolatiles vol = model->vol; + + XP_ASSERT( model != NULL ); + XP_MEMSET( model, 0, sizeof( *model ) ); + XP_MEMSET( &model->tiles, TILE_EMPTY_BIT, sizeof(model->tiles) ); + + model->nCols = nCols; + model->nRows = nRows; + + model->vol = vol; + + if ( !!model->vol.stack ) { + stack_init( model->vol.stack ); + } else { + model->vol.stack = stack_make( MPPARM(model->vol.mpool) + util_getVTManager(model->vol.util)); + } +} /* model_init */ + +void +model_destroy( ModelCtxt* model ) +{ + stack_destroy( model->vol.stack ); + /* is this it!? */ + XP_FREE( model->vol.mpool, model ); +} /* model_destroy */ + +static void +buildModelFromStack( ModelCtxt* model, StackCtxt* stack, + XWStreamCtxt* stream, + MovePrintFuncPre mpf_pre, MovePrintFuncPost mpf_post, + void* closure ) +{ + StackEntry entry; + XP_U16 i; + XP_S16 moveScore = 0; /* keep compiler happy */ + + for ( i = 0; stack_getNthEntry( stack, i, &entry ); ++i ) { + + if ( !!mpf_pre ) { + (*mpf_pre)( model, i, &entry, closure ); + } + + switch ( entry.moveType ) { + case MOVE_TYPE: + + model_makeTurnFromMoveInfo( model, entry.playerNum, + &entry.u.move.moveInfo); + moveScore = commitTurn( model, entry.playerNum, + &entry.u.move.newTiles, stream, XP_FALSE); + break; + case TRADE_TYPE: + makeTileTrade( model, entry.playerNum, &entry.u.trade.oldTiles, + &entry.u.trade.newTiles ); + break; + case ASSIGN_TYPE: + assignPlayerTiles( model, entry.playerNum, + &entry.u.assign.tiles ); + break; + case PHONY_TYPE: /* nothing to add */ + model_makeTurnFromMoveInfo( model, entry.playerNum, + &entry.u.phony.moveInfo); + /* do something here to cause it to print */ + (void)getCurrentMoveScoreIfLegal( model, entry.playerNum, stream, + &moveScore ); + moveScore = 0; + model_resetCurrentTurn( model, entry.playerNum ); + + break; + default: + XP_ASSERT(0); + } + + if ( !!mpf_post ) { + (*mpf_post)( model, i, &entry, moveScore, closure ); + } + } +} /* buildModelFromStack */ + +void +model_setNPlayers( ModelCtxt* model, XP_U16 nPlayers ) +{ + model->nPlayers = nPlayers; +} /* model_setNPlayers */ + +void +model_setDictionary( ModelCtxt* model, DictionaryCtxt* dict ) +{ + model->vol.dict = dict; +} /* model_setDictionary */ + +DictionaryCtxt* +model_getDictionary( ModelCtxt* model ) +{ + return model->vol.dict; +} /* model_getDictionary */ + +static XP_Bool +getPendingTileFor( ModelCtxt* model, XP_U16 turn, XP_U16 col, XP_U16 row, + CellTile* cellTile ) +{ + XP_Bool found = XP_FALSE; + PlayerCtxt* player; + PendingTile* pendings; + XP_U16 i; + + player = &model->players[turn]; + pendings = player->pendingTiles; + for ( i = 0; i < player->nPending; ++i ) { + + if ( (pendings->col == col) && (pendings->row == row) ) { + *cellTile = pendings->tile; + found = XP_TRUE; + XP_ASSERT ( (*cellTile & TILE_EMPTY_BIT) == 0 ); + break; + } + ++pendings; + } + + return found; +} /* getPendingTileFor */ + +XP_Bool +model_getTile( ModelCtxt* model, XP_U16 col, XP_U16 row, XP_Bool getPending, + XP_S16 turn, Tile* tileP, XP_Bool* isBlank, + XP_Bool* pendingP, XP_Bool* recentP ) +{ + CellTile cellTile = getModelTileRaw( model, col, row ); + XP_Bool pending = XP_FALSE; + + if ( (cellTile & TILE_PENDING_BIT) != 0 ) { + if ( getPending + && getPendingTileFor( model, turn, col, row, &cellTile ) ) { + + /* it's pending, but caller doesn't want to see it */ + pending = XP_TRUE; + } else { + cellTile = EMPTY_TILE; + } + } + + /* this needs to happen after the above b/c cellTile gets changed */ + if ( (cellTile & TILE_EMPTY_BIT) != 0 ) { + return XP_FALSE; + } + + *tileP = cellTile & TILE_VALUE_MASK; + *isBlank = IS_BLANK(cellTile); + *pendingP = pending; + if ( !!recentP ) { + *recentP = (cellTile & PREV_MOVE_BIT) != 0; + } + + return XP_TRUE; +} /* model_getTile */ + +void +model_listPlacedBlanks( ModelCtxt* model, XP_U16 turn, + XP_Bool includePending, BlankQueue* bcp ) +{ + XP_U16 nCols = model_numCols( model ); + XP_U16 nRows = model_numRows( model ); + XP_U16 col, row; + + XP_U16 nBlanks = 0; + + for ( row = 0; row < nRows; ++row ) { + for ( col = 0; col < nCols; ++col ) { + CellTile cellTile = getModelTileRaw( model, col, row ); + + if ( (cellTile & TILE_PENDING_BIT) != 0 ) { + if ( !includePending || + !getPendingTileFor( model, turn, col, row, &cellTile ) ) { + continue; + } + } + + if ( (cellTile & TILE_BLANK_BIT) != 0 ) { + bcp->col[nBlanks] = (XP_U8)col; + bcp->row[nBlanks] = (XP_U8)row; + ++nBlanks; + } + } + } + + bcp->nBlanks = nBlanks; +} /* model_listPlacedBlanks */ + +void +model_foreachPrevCell( ModelCtxt* model, BoardListener bl, void* closure ) +{ + XP_U16 col, row; + + for ( col = 0; col < model->nCols; ++col ) { + for ( row = 0; row < model->nRows; ++row) { + CellTile tile = getModelTileRaw( model, col, row ); + if ( (tile & PREV_MOVE_BIT) != 0 ) { + (*bl)( closure, (XP_U16)CELL_OWNER(tile), col, row, XP_FALSE ); + } + } + } +} /* model_foreachPrevCell */ + +static void +clearAndNotify( void* closure, XP_U16 turn, XP_U16 col, XP_U16 row, + XP_Bool added ) +{ + ModelCtxt* model = (ModelCtxt*)closure; + CellTile tile = getModelTileRaw( model, col, row ); + setModelTileRaw( model, col, row, (CellTile)(tile & ~PREV_MOVE_BIT) ); + + notifyBoardListeners( model, (XP_U16)CELL_OWNER(tile), col, row, + XP_FALSE ); +} /* clearAndNotify */ + +static void +clearLastMoveInfo( ModelCtxt* model ) +{ + model_foreachPrevCell( model, clearAndNotify, model ); +} /* clearLastMoveInfo */ + +static void +invalLastMove( ModelCtxt* model ) +{ + if ( !!model->vol.boardListenerFunc ) { + model_foreachPrevCell( model, model->vol.boardListenerFunc, + model->vol.boardListenerData ); + } +} /* invalLastMove */ + +void +model_foreachPendingCell( ModelCtxt* model, XP_S16 turn, + BoardListener bl, void* closure ) +{ + PendingTile* pt; + PlayerCtxt* player; + XP_S16 count; + + XP_ASSERT( turn >= 0 ); + player = &model->players[turn]; + count = player->nPending; + + for ( pt = player->pendingTiles; count--; ++pt ) { + XP_U16 col, row; + + col = pt->col; + row = pt->row; + + (*bl)( closure, turn, pt->col, pt->row, XP_FALSE ); + } +} /* model_invalPendingCells */ + +XP_U16 +model_getCellOwner( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + CellTile tile; + XP_U16 result; + + tile = getModelTileRaw( model, col, row ); + + result = CELL_OWNER(tile); + + return result; +} /* model_getCellOwner */ + +static void +setModelTileRaw( ModelCtxt* model, XP_U16 col, XP_U16 row, CellTile tile ) +{ + XP_ASSERT( col < MAX_COLS ); + XP_ASSERT( row < MAX_ROWS ); + model->tiles[col][row] = tile; +} /* model_setTile */ + +static CellTile +getModelTileRaw( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XP_ASSERT( col < MAX_COLS ); + XP_ASSERT( row < MAX_ROWS ); + return model->tiles[col][row]; +} /* getModelTileRaw */ + +static void +undoFromMoveInfo( ModelCtxt* model, XP_U16 turn, Tile blankTile, MoveInfo* mi ) +{ + XP_U16 col, row, i; + XP_U16* other; + MoveInfoTile* tinfo; + + col = row = mi->commonCoord; + other = mi->isHorizontal? &col: &row; + + for ( tinfo = mi->tiles, i = 0; i < mi->nTiles; ++tinfo, ++i ) { + Tile tile; + + *other = tinfo->varCoord; + tile = tinfo->tile; + + setModelTileRaw( model, col, row, EMPTY_TILE ); + notifyBoardListeners( model, turn, col, row, XP_FALSE ); + + if ( IS_BLANK(tile) ) { + tile = blankTile; + } + model_addPlayerTile( model, turn, -1, tile ); + } + + adjustScoreForUndone( model, mi, turn ); +} /* undoFromMoveInfo */ + +/* Remove tiles in a set from tray and put them back in the pool. + */ +static void +replaceNewTiles( ModelCtxt* model, PoolContext* pool, XP_U16 turn, + TrayTileSet* tileSet ) +{ + Tile* t; + XP_U16 i, nTiles; + + for ( t = tileSet->tiles, i = 0, nTiles = tileSet->nTiles; + i < nTiles; ++i ) { + XP_S16 index; + Tile tile = *t++; + + index = model_trayContains( model, turn, tile ); + XP_ASSERT( index >= 0 ); + model_removePlayerTile( model, turn, index ); + } + pool_replaceTiles( pool, tileSet); +} /* replaceNewTiles */ + +/* Turn the most recent move into a phony. + */ +void +model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, XP_U16* turn ) +{ + StackCtxt* stack = model->vol.stack; + StackEntry entry; + Tile blankTile = dict_getBlankTile( model_getDictionary(model) ); + + stack_popEntry( stack, &entry ); + XP_ASSERT( entry.moveType == MOVE_TYPE ); + + replaceNewTiles( model, pool, entry.playerNum, &entry.u.move.newTiles ); + undoFromMoveInfo( model, entry.playerNum, blankTile, + &entry.u.move.moveInfo ); + + stack_addPhony( stack, entry.playerNum, &entry.u.phony.moveInfo ); + + *turn = entry.playerNum; +} /* model_rejectPreviousMove */ + +/* Undo a move, but only if it's the move we're expecting to undo (as + * indicated by *moveNumP, if >= 0). + */ +XP_Bool +model_undoLatestMoves( ModelCtxt* model, PoolContext* pool, + XP_U16 nMovesSought, XP_U16* turnP, XP_S16* moveNumP ) +{ + StackCtxt* stack = model->vol.stack; + StackEntry entry; + XP_U16 turn = 0; + Tile blankTile = dict_getBlankTile( model_getDictionary(model) ); + XP_Bool success = XP_TRUE; + XP_S16 moveSought = *moveNumP; + XP_U16 nMovesUndone; + + if ( stack_getNEntries( stack ) < nMovesSought ) { + return XP_FALSE; + } + + for ( nMovesUndone = 0; success && nMovesUndone < nMovesSought; ) { + + success = stack_popEntry( stack, &entry ); + if ( success ) { + ++nMovesUndone; + + if ( moveSought < 0 ) { + moveSought = entry.moveNum; + } else if ( moveSought-- != entry.moveNum ) { + success = XP_FALSE; + break; + } + + turn = entry.playerNum; + model_resetCurrentTurn( model, turn ); + + if ( entry.moveType == MOVE_TYPE ) { + + /* get the tiles out of player's tray and back into the + pool */ + replaceNewTiles( model, pool, turn, &entry.u.move.newTiles ); + + undoFromMoveInfo( model, turn, blankTile, + &entry.u.move.moveInfo ); + } else if ( entry.moveType == TRADE_TYPE ) { + + replaceNewTiles( model, pool, turn, &entry.u.trade.newTiles ); + + pool_removeTiles( pool, &entry.u.trade.oldTiles ); + assignPlayerTiles( model, turn, &entry.u.trade.oldTiles ); + + } else if ( entry.moveType == PHONY_TYPE ) { + + /* nothing to do, since nothing happened */ + + } else { + success = XP_FALSE; + break; + } + } + } + + if ( nMovesUndone != nMovesSought ) { + success = XP_FALSE; + } + + if ( success ) { + *turnP = turn; + *moveNumP = entry.moveNum; + } else { + while ( nMovesUndone-- ) { + stack_redo( stack ); + } + } + + return success; +} /* model_undoLatestMoves */ + +void +model_trayToStream( ModelCtxt* model, XP_S16 turn, XWStreamCtxt* stream ) +{ + PlayerCtxt* player = &model->players[turn]; + + traySetToStream( stream, &player->trayTiles ); +} /* model_trayToStream */ + +void +model_currentMoveToStream( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream ) +{ + PlayerCtxt* player = &model->players[turn]; + XP_S16 numTiles = player->nPending; + + stream_putBits( stream, NTILES_NBITS, numTiles ); + + while ( numTiles-- ) { + Tile tile; + XP_U16 col, row; + XP_Bool isBlank; + + model_getCurrentMoveTile( model, turn, &numTiles, &tile, + &col, &row, &isBlank ); + XP_ASSERT( numTiles >= 0 ); + stream_putBits( stream, TILE_NBITS, tile ); + stream_putBits( stream, NUMCOLS_NBITS, col ); + stream_putBits( stream, NUMCOLS_NBITS, row ); + stream_putBits( stream, 1, isBlank ); + } +} /* model_turnToStream */ + +/* Take stream as the source of info about what tiles to move from tray to + * board. Undo any current move first -- a player on this device might be + * using the board as scratch during another player's turn. For each tile, + * assert that it's in the tray, remove it from the tray, and place it on the + * board. + */ +void +model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum, + XWStreamCtxt* stream ) +{ + XP_U16 numTiles; + Tile blank = dict_getBlankTile( model->vol.dict ); + + model_resetCurrentTurn( model, playerNum ); + + numTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS ); + + XP_STATUSF( "model_makeTurnFromStream: numTiles=%d\n", numTiles ); + + while ( numTiles-- ) { + XP_S16 foundAt; + Tile moveTile; + Tile tileFace = (Tile)stream_getBits( stream, TILE_NBITS ); + XP_U16 col = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + XP_U16 row = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + 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); + } +} /* model_makeMoveFromStream */ + +void +model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum, + MoveInfo* newMove ) +{ + XP_U16 col, row, i; + XP_U16* other; + MoveInfoTile* tinfo; + Tile blank; + XP_U16 numTiles; + + blank = dict_getBlankTile( model->vol.dict ); + numTiles = newMove->nTiles; + + col = row = newMove->commonCoord; /* just assign both */ + other = newMove->isHorizontal? &col: &row; + + for ( tinfo = newMove->tiles, i = 0; i < numTiles; ++i, ++tinfo ) { + XP_S16 tileIndex; + Tile tile = tinfo->tile; + + if ( IS_BLANK(tile) ) { + tile = blank; + } + + tileIndex = model_trayContains( model, playerNum, tile ); + + XP_ASSERT( tileIndex >= 0 ); + + *other = tinfo->varCoord; + model_moveTrayToBoard( model, (XP_S16)playerNum, col, row, tileIndex, + (Tile)(tinfo->tile & TILE_VALUE_MASK) ); + } +} /* model_makeTurnFromMoveInfo */ + +void +model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts ) +{ + PlayerCtxt* player; + XP_U16 nPlayers = model->nPlayers; + Tile blank; + + XP_ASSERT( !!model->vol.dict ); + blank = dict_getBlankTile( model->vol.dict ); + + for ( player = model->players; nPlayers--; ++player ) { + XP_U16 nTiles = player->nPending; + PendingTile* pt; + Tile* tiles; + + /* first the pending tiles */ + for ( pt = player->pendingTiles; nTiles--; ++pt ) { + Tile tile = pt->tile; + if ( IS_BLANK(tile) ) { + tile = blank; + } else { + tile &= TILE_VALUE_MASK; + } + XP_ASSERT( tile <= MAX_UNIQUE_TILES ); + ++counts[tile]; + } + + /* then the tiles still in the tray */ + nTiles = player->trayTiles.nTiles; + tiles = player->trayTiles.tiles; + while ( nTiles-- ) { + ++counts[*tiles++]; + } + } +} /* model_countAllTrayTiles */ + +XP_S16 +model_trayContains( ModelCtxt* model, XP_S16 turn, Tile tile ) +{ + PlayerCtxt* player; + XP_S16 i; + XP_S16 result = -1; + + XP_ASSERT( turn >= 0 ); + XP_ASSERT( turn < model->nPlayers ); + + player = &model->players[turn]; + + for ( i = 0; i < player->trayTiles.nTiles; ++i ) { + Tile playerTile = player->trayTiles.tiles[i]; + if ( playerTile == tile ) { + result = i; + break; + } + } + + return result; +} /* model_trayContains */ + +XP_U16 +model_getCurrentMoveCount( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player; + XP_ASSERT( turn >= 0 ); + player = &model->players[turn]; + return player->nPending; +} /* model_getCurrentMoveCount */ + +void +model_getCurrentMoveTile( ModelCtxt* model, XP_S16 turn, XP_S16* index, + Tile* tile, XP_U16* col, XP_U16* row, + XP_Bool* isBlank ) +{ + PlayerCtxt* player; + PendingTile* pt; + XP_ASSERT( turn >= 0 ); + + player = &model->players[turn]; + XP_ASSERT( *index < player->nPending ); + + if ( *index < 0 ) { + *index = player->nPending - 1; + } + + pt = &player->pendingTiles[*index]; + + *col = pt->col; + *row = pt->row; + *isBlank = (pt->tile & TILE_BLANK_BIT) != 0; + *tile = pt->tile & TILE_VALUE_MASK; +} /* model_getCurrentMoveTile */ + +Tile +model_removePlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ) +{ + PlayerCtxt* player = &model->players[turn]; + Tile tile; + short i; + TileBit bits = 0; + + if ( index < 0 ) { + index = player->trayTiles.nTiles - 1; + } else { + XP_ASSERT( index < player->trayTiles.nTiles ); + } + + tile = player->trayTiles.tiles[index]; + bits = 1 << index; + + --player->trayTiles.nTiles; + for ( i = index; i < player->trayTiles.nTiles; ++i ) { + player->trayTiles.tiles[i] = player->trayTiles.tiles[i+1]; + bits |= 3 << i; + } + + notifyTrayListeners( model, turn, bits ); + return tile; +} /* model_removePlayerTile */ + +void +model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, + XP_S16 tileIndex, Tile blankFace ) +{ + PlayerCtxt* player; + PendingTile* pt; + + Tile tile = model_removePlayerTile( model, turn, tileIndex ); + + if ( tile == dict_getBlankTile(model->vol.dict) ) { + if ( blankFace != EMPTY_TILE ) { + tile = blankFace; + } else { + XP_UCHAR face[4]; + util_askBlankFace( model->vol.util, model->vol.dict, face ); + tile = dict_tileForString( model->vol.dict, face ); + } + tile |= TILE_BLANK_BIT; + } + + player = &model->players[turn]; + + if ( player->nPending == 0 ) { + invalLastMove( model ); + } + + pt = &player->pendingTiles[player->nPending++]; + XP_ASSERT( player->nPending <= MAX_TRAY_TILES ); + + pt->tile = tile; + pt->col = (XP_U8)col; + pt->row = (XP_U8)row; + + invalidateScore( model, turn ); + incrPendingTileCountAt( model, col, row ); + + notifyBoardListeners( model, turn, col, row, XP_TRUE ); +} /* model_moveTrayToBoard */ + +void +model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, XP_S16 index ) +{ + PlayerCtxt* player; + short i; + PendingTile* pt; + Tile tile; + + player = &model->players[turn]; + if ( index < 0 ) { + index = player->nPending - 1; + } + + pt = &player->pendingTiles[index]; + decrPendingTileCountAt( model, pt->col, pt->row ); + notifyBoardListeners( model, turn, pt->col, pt->row, XP_FALSE ); + tile = pt->tile; + + if ( (tile & TILE_BLANK_BIT) != 0 ) { + tile = dict_getBlankTile( model->vol.dict ); + } + + model_addPlayerTile( model, turn, -1, tile ); + + --player->nPending; + for ( i = index; i < player->nPending; ++i ) { + player->pendingTiles[i] = player->pendingTiles[i+1]; + } + + if ( player->nPending == 0 ) { + invalLastMove( model ); + } + + invalidateScore( model, turn ); +} /* model_moveBoardToTray */ + +void +model_resetCurrentTurn( ModelCtxt* model, XP_S16 whose ) +{ + PlayerCtxt* player; + + XP_ASSERT( whose >= 0 && whose < model->nPlayers ); + player = &model->players[whose]; + + while ( player->nPending > 0 ) { + model_moveBoardToTray( model, whose, -1 ); + } +} /* model_resetCurrentTurn */ + +static void +incrPendingTileCountAt( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XP_U16 val = getModelTileRaw( model, col, row ); + + if ( TILE_IS_EMPTY(val) ) { + val = 0; + } else { + XP_ASSERT( (val & TILE_PENDING_BIT) != 0 ); + XP_ASSERT( (val & TILE_VALUE_MASK) > 0 ); + } + + ++val; + XP_ASSERT( (val & TILE_VALUE_MASK) > 0 && + (val & TILE_VALUE_MASK) <= MAX_NUM_PLAYERS ); + setModelTileRaw( model, col, row, (CellTile)(val | TILE_PENDING_BIT) ); +} /* incrPendingTileCountAt */ + +static void +setPendingCounts( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + PendingTile* pending = player->pendingTiles; + XP_U16 nPending; + + for ( nPending = player->nPending; nPending--; ) { + incrPendingTileCountAt( model, pending->col, pending->row ); + ++pending; + } + +} /* setPendingCounts */ + +static void +decrPendingTileCountAt( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XP_U16 val = getModelTileRaw( model, col, row ); + + /* for pending tiles, the value is defined in the players array, so what + we keep here is a refcount of how many players have put tiles there. */ + val &= TILE_VALUE_MASK; /* the refcount */ + XP_ASSERT( val <= MAX_NUM_PLAYERS && val > 0 ); + if ( --val > 0 ) { + val |= TILE_PENDING_BIT; + } else { + val = EMPTY_TILE; + } + setModelTileRaw( model, col, row, val ); +} /* decrPendingTileCountAt */ + +static void +putBackOtherPlayersTiles( ModelCtxt* model, XP_U16 notMyTurn, + XP_U16 col, XP_U16 row ) +{ + XP_S16 turn, j; + + for ( turn = 0; turn < model->nPlayers; ++turn ) { + PlayerCtxt* player; + + if ( turn == notMyTurn ) { + continue; + } + + player = &model->players[turn]; + for ( j = player->nPending-1; j >= 0; --j ) { /* backwards in case + removed */ + PendingTile* pt = &player->pendingTiles[j]; + if ( pt->col == col && pt->row == row ) { + /* this one needs to be put back */ + + model_moveBoardToTray( model, turn, j ); + + break; /* a player can have only one tile on a square */ + } + } + } +} /* putBackOtherPlayersTiles */ + +/* Make those tiles placed by 'turn' a permanent part of the board. If any + * other players have placed pending tiles on those same squares, replace them + * in their trays. + */ +static XP_S16 +commitTurn( ModelCtxt* model, XP_S16 turn, TrayTileSet* newTiles, + XWStreamCtxt* stream, XP_Bool useStack ) +{ + short i; + PlayerCtxt* player; + PendingTile* pt; + XP_S16 score; + XP_Bool inLine, isHorizontal; + Tile* newTilesP; + XP_U16 nTiles; + + nTiles = newTiles->nTiles; + +#ifdef DEBUG + XP_ASSERT( getCurrentMoveScoreIfLegal( model, turn, (XWStreamCtxt*)NULL, + &score ) ); + invalidateScore( model, turn ); +#endif + + XP_ASSERT( turn >= 0 && turn < MAX_NUM_PLAYERS); + + clearLastMoveInfo( model ); + + player = &model->players[turn]; + + if ( useStack ) { + MoveInfo moveInfo; + inLine = tilesInLine( model, turn, &isHorizontal ); + XP_ASSERT( inLine ); + normalizeMoves( model, turn, isHorizontal, &moveInfo ); + + stack_addMove( model->vol.stack, turn, &moveInfo, newTiles ); + } + + for ( i = 0, pt=player->pendingTiles; i < player->nPending; ++i, ++pt ) { + XP_U16 col, row; + CellTile tile; + XP_U16 val; + + col = pt->col; + row = pt->row; + tile = getModelTileRaw( model, col, row ); + + XP_ASSERT( tile & TILE_PENDING_BIT ); + + val = tile & TILE_VALUE_MASK; + if ( val > 1 ) { /* somebody else is using this square too! */ + putBackOtherPlayersTiles( model, turn, col, row ); + } + + tile = pt->tile; + tile |= PREV_MOVE_BIT; + tile |= turn << CELL_OWNER_OFFSET; + + setModelTileRaw( model, col, row, tile ); + + notifyBoardListeners( model, turn, col, row, XP_FALSE ); + } + + (void)getCurrentMoveScoreIfLegal( model, turn, stream, &score ); + XP_ASSERT( score >= 0 ); + player->score += score; + + /* Why is this next loop necessary? */ + for ( i = 0; i < model->nPlayers; ++i ) { + invalidateScore( model, i ); + } + + player->nPending = 0; + + newTilesP = newTiles->tiles; + while ( nTiles-- ) { + model_addPlayerTile( model, turn, -1, *newTilesP++ ); + } + + return score; +} /* commitTurn */ + +void +model_commitTurn( ModelCtxt* model, XP_S16 turn, TrayTileSet* newTiles ) +{ + (void)commitTurn( model, turn, newTiles, (XWStreamCtxt*)NULL, XP_TRUE ); +} /* model_commitTurn */ + +/* Given a rack of new tiles and of old, remove all the old from the tray and + * replace them with new. Replace in the same place so that user sees an + * in-place change. + */ +static void +makeTileTrade( ModelCtxt* model, XP_S16 player, TrayTileSet* oldTiles, + TrayTileSet* newTiles ) +{ + XP_U16 i; + XP_U16 nTiles; + + XP_ASSERT( newTiles->nTiles == oldTiles->nTiles ); + + for ( nTiles = newTiles->nTiles, i = 0; i < nTiles; ++i ) { + Tile oldTile = oldTiles->tiles[i]; + + XP_S16 tileIndex = model_trayContains( model, player, oldTile ); + XP_ASSERT( tileIndex >= 0 ); + model_removePlayerTile( model, player, tileIndex ); + model_addPlayerTile( model, player, tileIndex, newTiles->tiles[i] ); + } +} /* makeTileTrade */ + +void +model_makeTileTrade( ModelCtxt* model, XP_S16 player, + TrayTileSet* oldTiles, TrayTileSet* newTiles ) +{ + stack_addTrade( model->vol.stack, player, oldTiles, newTiles ); + + makeTileTrade( model, player, oldTiles, newTiles ); +} /* model_makeTileTrade */ + +Tile +model_getPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ) +{ + PlayerCtxt* player = &model->players[turn]; + + if ( index < 0 ) { + index = player->trayTiles.nTiles-1; + } + + XP_ASSERT( index < player->trayTiles.nTiles ); + + return player->trayTiles.tiles[index]; +} /* model_getPlayerTile */ + +const TrayTileSet* +model_getPlayerTiles( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + + return (const TrayTileSet*)&player->trayTiles; +} /* model_getPlayerTile */ + +void +model_addPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index, Tile tile ) +{ + PlayerCtxt* player = &model->players[turn]; + short i; + TileBit bits = 0; + + XP_ASSERT( player->trayTiles.nTiles < MAX_TRAY_TILES ); + + if ( index < 0 ) { + index = player->trayTiles.nTiles; + } + + /* move tiles up to make room */ + for ( i = player->trayTiles.nTiles; i > index; --i ) { + player->trayTiles.tiles[i] = player->trayTiles.tiles[i-1]; + bits |= (3 << (i-2)); + } + ++player->trayTiles.nTiles; + player->trayTiles.tiles[index] = tile; + + bits |= (1 << index); + notifyTrayListeners( model, turn, bits ); +} /* model_addPlayerTile */ + +static void +assignPlayerTiles( ModelCtxt* model, XP_S16 turn, TrayTileSet* tiles ) +{ + Tile* tilep = tiles->tiles; + XP_U16 nTiles = tiles->nTiles; + while ( nTiles-- ) { + model_addPlayerTile( model, turn, -1, *tilep++ ); + } +} /* model_addPlayerTiles */ + +void +model_assignPlayerTiles( ModelCtxt* model, XP_S16 turn, TrayTileSet* tiles ) +{ + stack_addAssign( model->vol.stack, turn, tiles ); + + assignPlayerTiles( model, turn, tiles ); +} /* model_addPlayerTiles */ + +XP_U16 +model_getNumTilesInTray( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + return player->trayTiles.nTiles; +} /* model_getNumPlayerTiles */ + +XP_U16 +model_getNumTilesTotal( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + return player->trayTiles.nTiles + player->nPending; +} /* model_getNumTilesTotal */ + +XP_U16 +model_numRows( ModelCtxt* model ) +{ + return model->nRows; +} /* model_numRows */ + +XP_U16 +model_numCols( ModelCtxt* model ) +{ + return model->nCols; +} /* model_numCols */ + +void +model_setBoardListener( ModelCtxt* model, BoardListener bl, void* data ) +{ + model->vol.boardListenerFunc = bl; + model->vol.boardListenerData = data; +} /* model_setBoardListener */ + +void +model_setTrayListener( ModelCtxt* model, TrayListener tl, void* data ) +{ + model->vol.trayListenerFunc = tl; + model->vol.trayListenerData = data; +} /* model_setBoardListener */ + +static void +notifyBoardListeners( ModelCtxt* model, XP_U16 turn, XP_U16 col, XP_U16 row, + XP_Bool added ) +{ + if ( model->vol.boardListenerFunc != NULL ) { + (*model->vol.boardListenerFunc)( model->vol.boardListenerData, turn, + col, row, added ); + } +} /* notifyBoardListeners */ + +static void +notifyTrayListeners( ModelCtxt* model, XP_U16 turn, TileBit bits ) +{ + if ( model->vol.trayListenerFunc != NULL ) { + (*model->vol.trayListenerFunc)( model->vol.trayListenerData, turn, + bits ); + } +} /* notifyTrayListeners */ + +static void +printString( XWStreamCtxt* stream, XP_UCHAR* str ) +{ + stream_putBytes( stream, str, (XP_U16)XP_STRLEN((char*)str) ); +} /* printString */ + +static XP_UCHAR* +formatTray( const TrayTileSet* tiles, DictionaryCtxt* dict, XP_UCHAR* buf, + XP_Bool keepHidden ) +{ + if ( keepHidden ) { + XP_U16 i; + for ( i = 0; i < tiles->nTiles; ++i ) { + buf[i] = '?'; + } + buf[i] = '\0'; + } else { + dict_tilesToString( dict, (Tile*)tiles->tiles, tiles->nTiles, buf ); + } + + return buf; +} /* formatTray */ + +typedef struct MovePrintClosure { + XWStreamCtxt* stream; + DictionaryCtxt* dict; + XP_U16 nPrinted; + XP_Bool keepHidden; +} MovePrintClosure; + +static void +printMovePre( ModelCtxt* model, XP_U16 moveN, StackEntry* entry, + void* p_closure ) +{ + XWStreamCtxt* stream; + XP_UCHAR* format; + XP_UCHAR buf[32]; + XP_UCHAR traybuf[MAX_TRAY_TILES+1]; + MovePrintClosure* closure = (MovePrintClosure*)p_closure; + + if ( entry->moveType == ASSIGN_TYPE ) { + return; + } + + stream = closure->stream; + + XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"%d:%d ", ++closure->nPrinted, + entry->playerNum+1 ); + printString( stream, (XP_UCHAR*)buf ); + + if ( entry->moveType == TRADE_TYPE ) { + } else { + XP_UCHAR letter[2] = {'\0','\0'}; + XP_Bool isHorizontal = entry->u.move.moveInfo.isHorizontal; + XP_U16 col, row; + MoveInfo* mi; + XP_Bool isPass = XP_FALSE; + + if ( entry->moveType == PHONY_TYPE ) { + mi = &entry->u.phony.moveInfo; + } else { + mi = &entry->u.move.moveInfo; + if ( mi->nTiles == 0 ) { + isPass = XP_TRUE; + } + } + + if ( isPass ) { + format = util_getUserString( model->vol.util, STR_PASS ); + XP_SNPRINTF( buf, sizeof(buf), format ); + } else { + if ( isHorizontal ) { + format = util_getUserString( model->vol.util, STRS_MOVE_ACROSS ); + } else { + format = util_getUserString( model->vol.util, STRS_MOVE_DOWN ); + } + + row = mi->commonCoord; + col = mi->tiles[0].varCoord; + if ( !isHorizontal ) { + XP_U16 tmp = col; col = row; row = tmp; + } + letter[0] = 'A' + col; + + XP_SNPRINTF( traybuf, sizeof(traybuf), (XP_UCHAR *)"%s%d", + letter, row + 1 ); + XP_SNPRINTF( buf, sizeof(buf), format, traybuf ); + } + printString( stream, (XP_UCHAR*)buf ); + } + + if ( !closure->keepHidden ) { + format = util_getUserString( model->vol.util, STRS_TRAY_AT_START ); + formatTray( model_getPlayerTiles( model, + entry->playerNum ), + closure->dict, (XP_UCHAR*)traybuf, XP_FALSE ); + XP_SNPRINTF( buf, sizeof(buf), format, traybuf ); + printString( stream, buf ); + } + +} /* printMovePre */ + +static void +printMovePost( ModelCtxt* model, XP_U16 moveN, StackEntry* entry, + XP_S16 score, void* p_closure ) +{ + MovePrintClosure* closure = (MovePrintClosure*)p_closure; + XWStreamCtxt* stream = closure->stream; + DictionaryCtxt* dict = closure->dict; + XP_UCHAR* format; + XP_U16 nTiles; + XP_S16 totalScore; + XP_UCHAR buf[100]; + XP_UCHAR traybuf1[MAX_TRAY_TILES+1]; + XP_UCHAR traybuf2[MAX_TRAY_TILES+1]; + MoveInfo* mi; + + if ( entry->moveType == ASSIGN_TYPE ) { + return; + } + + totalScore = model_getPlayerScore( model, entry->playerNum ); + + switch( entry->moveType ) { + case TRADE_TYPE: + formatTray( (const TrayTileSet*)&entry->u.trade.oldTiles, + dict, traybuf1, closure->keepHidden ); + formatTray( (const TrayTileSet*) &entry->u.trade.newTiles, + dict, traybuf2, closure->keepHidden ); + + format = util_getUserString( model->vol.util, STRSS_TRADED_FOR ); + XP_SNPRINTF( buf, sizeof(buf), format, traybuf1, traybuf2 ); + printString( stream, buf ); + printString( stream, (XP_UCHAR*)XP_CR ); + break; + + case PHONY_TYPE: + format = util_getUserString( model->vol.util, STR_PHONY_REJECTED ); + printString( stream, format ); + case MOVE_TYPE: + format = util_getUserString( model->vol.util, STRD_CUMULATIVE_SCORE ); + XP_SNPRINTF( buf, sizeof(buf), format, totalScore ); + printString( stream, buf ); + + if ( entry->moveType == PHONY_TYPE ) { + mi = &entry->u.phony.moveInfo; + } else { + mi = &entry->u.move.moveInfo; + } + nTiles = mi->nTiles; + if ( nTiles > 0 ) { + + if ( entry->moveType == PHONY_TYPE ) { + /* printString( stream, (XP_UCHAR*)"phony rejected " ); */ + } else if ( !closure->keepHidden ) { + format = util_getUserString(model->vol.util, STRS_NEW_TILES); + XP_SNPRINTF( buf, sizeof(buf), format, + formatTray( &entry->u.move.newTiles, dict, + traybuf1, XP_FALSE ) ); + printString( stream, buf ); + } + } + + break; + } + + printString( stream, (XP_UCHAR*)XP_CR ); +} /* printMovePost */ + +void +model_writeGameHistory( ModelCtxt* model, XWStreamCtxt* stream, + ServerCtxt* server, XP_Bool gameOver ) +{ + ModelCtxt* tmpModel; + StackCtxt* stack = model->vol.stack; + MovePrintClosure closure; + + closure.stream = stream; + closure.dict = model_getDictionary( model ); + closure.keepHidden = !gameOver; + closure.nPrinted = 0; + + tmpModel = model_make( MPPARM(model->vol.mpool) + model_getDictionary(model), + model->vol.util, model_numCols(model), + model_numRows(model)); + model_setNPlayers( tmpModel, model->nPlayers ); + + buildModelFromStack( tmpModel, stack, stream, + printMovePre, printMovePost, + &closure ); + + if ( gameOver ) { + /* if the game's over, it shouldn't matter which model I pass to this + method */ + server_writeFinalScores( server, stream ); + } + + model_destroy( tmpModel ); +} /* model_writeGameHistory */ + +static void +loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) +{ + XP_U16 i; + + pc->curMoveValid = stream_getBits( stream, 1 ); + + traySetFromStream( stream, &pc->trayTiles ); + + pc->nPending = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + + for ( i = 0; i < pc->nPending; ++i ) { + PendingTile* pt = &pc->pendingTiles[i]; + pt->col = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + pt->row = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + pt->tile = (Tile)stream_getBits( stream, 6 ); + } + +} /* loadPlayerCtxt */ + +static void +writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) +{ + XP_U16 i; + + stream_putBits( stream, 1, pc->curMoveValid ); + + traySetToStream( stream, &pc->trayTiles ); + + stream_putBits( stream, NTILES_NBITS, pc->nPending ); + + for ( i = 0; i < pc->nPending; ++i ) { + PendingTile* pt = &pc->pendingTiles[i]; + stream_putBits( stream, NUMCOLS_NBITS, pt->col ); + stream_putBits( stream, NUMCOLS_NBITS, pt->row ); + stream_putBits( stream, 6, pt->tile ); + } + +} /* loadPlayerCtxt */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/model.h b/xwords4/common/model.h new file mode 100644 index 000000000..ddcf9dd0a --- /dev/null +++ b/xwords4/common/model.h @@ -0,0 +1,238 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _MODEL_H_ +#define _MODEL_H_ + +#include "comtypes.h" +/* #include "xptypes.h" */ +#include "dictnry.h" +#include "mempool.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define MAX_ROWS 16 +#define MAX_COLS 16 +#define NUMCOLS_NBITS 4 + +#ifdef EIGHT_TILES +#define MAX_TRAY_TILES 8 +#define NTILES_NBITS 4 +#else +#define MAX_TRAY_TILES 7 +#define NTILES_NBITS 3 +#endif + +#define MIN_TRADE_TILES MAX_TRAY_TILES +/* apply to CellTile */ +#define TILE_VALUE_MASK 0x001F +#define TILE_BLANK_BIT 0x0020 +#define IS_BLANK(t) (((t)&TILE_BLANK_BIT)!= 0) +#define TILE_EMPTY_BIT 0x0040 +#define TILE_PENDING_BIT 0x0080 +#define PREV_MOVE_BIT 0x100 + +#define CELL_OWNER_MASK 0x0600 +#define CELL_OWNER_OFFSET 9 +#define CELL_OWNER(t) (((t)&CELL_OWNER_MASK) >> CELL_OWNER_OFFSET) + +#define MAX_UNIQUE_TILES 32 /* max tile non-blank faces */ +#define MAX_NUM_BLANKS 4 + +/* Used by scoring code and engine as fast representation of moves. */ +typedef struct MoveInfoTile { + XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */ + Tile tile; /* 5 bits will do */ +} MoveInfoTile; + +typedef struct MoveInfo { + XP_U8 nTiles; /* 4 bits: 0-7 */ + XP_U8 commonCoord; /* 5 bits: 0-16 if 17x17 possible */ + XP_Bool isHorizontal; /* 1 bit */ + /* If this is to go on an undo stack, we need player num here, or the code + has to keep track of it *and* there must be exactly one entry per + player per turn. */ + MoveInfoTile tiles[MAX_TRAY_TILES]; +} MoveInfo; + +typedef XP_U8 TrayTile; +typedef struct TrayTileSet { + XP_U8 nTiles; + TrayTile tiles[MAX_TRAY_TILES]; +} TrayTileSet; + +typedef struct BlankQueue { + XP_U16 nBlanks; + XP_U8 col[MAX_NUM_BLANKS]; + XP_U8 row[MAX_NUM_BLANKS]; +} BlankQueue; + +typedef XP_U8 TileBit; /* bits indicating selection of tiles in tray */ +#define ALLTILES ((TileBit)~(0xFF<<(MAX_TRAY_TILES))) + +#define ILLEGAL_MOVE_SCORE (-1) + +#define EMPTY_TILE TILE_EMPTY_BIT +#define TILE_IS_EMPTY(t) (((t)&TILE_EMPTY_BIT)!=0) +#define REVERSED_TILE TILE_PENDING_BIT /* reuse that bit for tile drawing + only */ + + +ModelCtxt* model_make( MPFORMAL DictionaryCtxt* dict, XW_UtilCtxt* util, + XP_U16 nCols, XP_U16 nRows ); + +ModelCtxt* model_makeFromStream( MPFORMAL XWStreamCtxt* stream, + DictionaryCtxt* dict, XW_UtilCtxt* util ); + +void model_writeToStream( ModelCtxt* model, XWStreamCtxt* stream ); + +void model_init( ModelCtxt* model, XP_U16 nCols, XP_U16 nRows ); +void model_destroy( ModelCtxt* model ); +void model_setNPlayers( ModelCtxt* model, XP_U16 numPlayers ); + +void model_setDictionary( ModelCtxt* model, DictionaryCtxt* dict ); +DictionaryCtxt* model_getDictionary( ModelCtxt* model ); + +XP_Bool model_getTile( ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool getPending, XP_S16 turn, + Tile* tile, XP_Bool* isBlank, + XP_Bool* isPending, XP_Bool* isRecent ); + +void model_listPlacedBlanks( ModelCtxt* model, XP_U16 turn, + XP_Bool includePending, BlankQueue* bcp ); + +XP_U16 model_getCellOwner( ModelCtxt* model, XP_U16 col, XP_U16 row ); + +void model_assignPlayerTiles( ModelCtxt* model, XP_S16 turn, + TrayTileSet* tiles ); +Tile model_getPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ); + +Tile model_removePlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ); +void model_addPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index, + Tile tile ); + +/* As an optimization, return a pointer to the model's array of tiles for a + player. Don't even think about modifying the array!!!! */ +const TrayTileSet* model_getPlayerTiles( ModelCtxt* model, XP_S16 turn ); + +XP_U16 model_getNumTilesInTray( ModelCtxt* model, XP_S16 turn ); +XP_U16 model_getNumTilesTotal( ModelCtxt* model, XP_S16 turn ); +void model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, XP_S16 index ); +void model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, + XP_U16 row, XP_S16 tileIndex, Tile blankFace ); +XP_S16 model_trayContains( ModelCtxt* model, XP_S16 turn, Tile tile ); + + +/* void model_setTile( ModelCtxt* model, XP_U16 col, XP_U16 row, */ +/* Tile tile, XP_Bool isBlank ); */ +XP_U16 model_numRows( ModelCtxt* model ); +XP_U16 model_numCols( ModelCtxt* model ); + +/* XP_U16 model_numTilesCurrentTray( ModelCtxt* model ); */ +/* Tile model_currentTrayTile( ModelCtxt* model, XP_U16 index ); */ +void model_addToCurrentMove( ModelCtxt* model, XP_S16 turn, + XP_U16 col, XP_U16 row, + Tile tile, XP_Bool isBlank ); +XP_U16 model_getCurrentMoveCount( ModelCtxt* model, XP_S16 turn ); + +void model_getCurrentMoveTile( ModelCtxt* model, XP_S16 turn, XP_S16* index, + Tile* tile, XP_U16* col, XP_U16* row, + XP_Bool* isBlank ); + +void model_commitTurn( ModelCtxt* model, XP_S16 player, + TrayTileSet* newTiles ); +void model_commitRejectedPhony( ModelCtxt* model, XP_S16 player ); +void model_makeTileTrade( ModelCtxt* model, XP_S16 player, + TrayTileSet* oldTiles, TrayTileSet* newTiles ); + +XP_Bool model_undoLatestMoves( ModelCtxt* model, PoolContext* pool, + XP_U16 nMovesSought, XP_U16* turn, + XP_S16* moveNum ); +void model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, + XP_U16* turn ); + +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 ); +void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum, + MoveInfo* newMove ); + +void model_resetCurrentTurn( ModelCtxt* model, XP_S16 turn ); + +/********************* notification ********************/ +typedef void (*BoardListener)(void* data, XP_U16 turn, XP_U16 col, + XP_U16 row, XP_Bool added ); +void model_setBoardListener( ModelCtxt* model, BoardListener bl, + void* data ); +typedef void (*TrayListener)(void* data, XP_U16 turn, TileBit bits ); +void model_setTrayListener( ModelCtxt* model, TrayListener bl, + void* data ); +void model_foreachPendingCell( ModelCtxt* model, XP_S16 turn, + BoardListener bl, void* data ); +void model_foreachPrevCell( ModelCtxt* model, BoardListener bl, void* data ); + +void model_writeGameHistory( ModelCtxt* model, XWStreamCtxt* stream, + ServerCtxt* server, /* for player names */ + XP_Bool gameOver ); + +/* for the tile values dialog: total all the tiles in players trays and + tentatively placed on the board. */ +void model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts ); + +/********************* scoring ********************/ + +typedef XP_Bool (*WordNotifierProc)( XP_UCHAR* word, void* closure ); +typedef struct WordNotifierInfo { + WordNotifierProc proc; + void* closure; +} WordNotifierInfo; + +XP_Bool getCurrentMoveScoreIfLegal( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream, XP_S16* score ); +XP_S16 model_getPlayerScore( ModelCtxt* model, XP_S16 player ); +XP_Bool model_checkMoveLegal( ModelCtxt* model, XP_S16 player, + XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo ); + +void model_figureFinalScores( ModelCtxt* model, XP_S16* scores, + XP_S16* tilePenalties ); + +/* figureMoveScore is meant only for the engine's use */ +XP_U16 figureMoveScore( ModelCtxt* model, MoveInfo* moveInfo, + EngineCtxt* engine, XWStreamCtxt* stream, + XP_Bool silent, WordNotifierInfo* notifyInfo ); + +/********************* persistence ********************/ +#ifdef INCLUDE_IO_SUPPORT +void model_load( ModelCtxt* model, XP_Stream* inStream ); +void model_store( ModelCtxt* model, XP_Stream* outStream ); +#endif + + +#ifdef CPLUS +} +#endif + +#endif + diff --git a/xwords4/common/modelp.h b/xwords4/common/modelp.h new file mode 100644 index 000000000..e8622a34f --- /dev/null +++ b/xwords4/common/modelp.h @@ -0,0 +1,78 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _MODELP_H_ +#define _MODELP_H_ + +#include "model.h" +#include "movestak.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct PendingTile { + XP_U8 col; + XP_U8 row; + Tile tile; /* includes face and blank bit */ +} PendingTile; + +typedef struct PlayerCtxt { + XP_S16 score; + XP_S16 curMoveScore; /* negative means illegal */ + XP_Bool curMoveValid; + TrayTileSet trayTiles; + XP_U8 nPending; /* still in tray but "on board" */ + PendingTile pendingTiles[MAX_TRAY_TILES]; +} PlayerCtxt; + +typedef struct ModelVolatiles { + XW_UtilCtxt* util; + struct CurGameInfo* gi; + DictionaryCtxt* dict; + BoardListener boardListenerFunc; + StackCtxt* stack; + void* boardListenerData; + TrayListener trayListenerFunc; + void* trayListenerData; + MPSLOT +} ModelVolatiles; + +struct ModelCtxt { + + ModelVolatiles vol; + + CellTile tiles[MAX_COLS][MAX_ROWS]; + + PlayerCtxt players[MAX_NUM_PLAYERS]; + XP_U16 nPlayers; + XP_U16 nCols; + XP_U16 nRows; +}; + +void invalidateScore( ModelCtxt* model, XP_S16 player ); +XP_Bool tilesInLine( ModelCtxt* model, XP_S16 turn, XP_Bool* isHorizontal ); +void normalizeMoves( ModelCtxt* model, XP_S16 turn, + XP_Bool isHorizontal, MoveInfo* moveInfo ); +void adjustScoreForUndone( ModelCtxt* model, MoveInfo* mi, XP_U16 turn ); +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c new file mode 100644 index 000000000..e6418631c --- /dev/null +++ b/xwords4/common/movestak.c @@ -0,0 +1,351 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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 "modelp.h" + +#include "mempool.h" +#include "xwstream.h" +#include "movestak.h" +#include "memstream.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +struct StackCtxt { + VTableMgr* vtmgr; + + XWStreamCtxt* data; + + XWStreamPos top; + + XWStreamPos cachedPos; + + XP_U16 cacheNext; + XP_U16 nEntries; + XP_U16 highWaterMark; + + 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 */ + +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 ); + } + 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( MPPARM(stack->mpool) stack->vtmgr, + NULL, 0, + (MemStreamCloseCallback)NULL ); + + stream_copyFromStream( stack->data, stream, nBytes ); + } else { + XP_ASSERT( stack->nEntries == 0 ); + XP_ASSERT( stack->top == 0 ); + } +} /* stack_makeFromStream */ + +void +stack_writeToStream( StackCtxt* stack, XWStreamCtxt* stream ) +{ + XP_U16 nBytes = !!stack->data? stream_getSize( stack->data ): 0; + + stream_putU16( stream, nBytes ); + + if ( nBytes > 0 ) { + stream_putU16( stream, stack->highWaterMark ); + stream_putU16( stream, stack->nEntries ); + stream_putU32( stream, stack->top ); + + stream_setPos( stack->data, START_OF_STREAM, POS_READ ); + stream_copyFromStream( stream, stack->data, nBytes ); + } +} /* stack_writeToStream */ + +static void +pushEntry( StackCtxt* stack, StackEntry* entry ) +{ + XP_U16 i; + XWStreamPos oldLoc; + XP_U16 nTiles = entry->u.move.moveInfo.nTiles; + XWStreamCtxt* stream = stack->data; + + if ( !stream ) { + stream = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, NULL, 0, + (MemStreamCloseCallback)NULL ); + stack->data = stream; + } + + oldLoc = stream_setPos( stream, stack->top, POS_WRITE ); + + 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 ); + for ( i = 0; i < nTiles; ++i ) { + stream_putBits( stream, 5, + entry->u.move.moveInfo.tiles[i].varCoord ); + stream_putBits( stream, 6, entry->u.move.moveInfo.tiles[i].tile ); + } + 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, oldLoc, POS_WRITE ); +} /* pushEntry */ + +static void +readEntry( StackCtxt* stack, StackEntry* entry ) +{ + XP_U16 nTiles, i; + 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); + for ( i = 0; i < nTiles; ++i ) { + entry->u.move.moveInfo.tiles[i].varCoord = + (XP_U8)stream_getBits(stream, 5); + entry->u.move.moveInfo.tiles[i].tile = + (Tile)stream_getBits( stream, 6 ); + } + + 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, MoveInfo* moveInfo, + TrayTileSet* newTiles ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = MOVE_TYPE; + + 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, MoveInfo* moveInfo ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = PHONY_TYPE; + + XP_MEMCPY( &move.u.phony.moveInfo, moveInfo, + sizeof(move.u.phony.moveInfo)); + + pushEntry( stack, &move ); +} /* stack_addPhony */ + +void +stack_addTrade( StackCtxt* stack, XP_U16 turn, + TrayTileSet* oldTiles, TrayTileSet* newTiles ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = TRADE_TYPE; + + move.u.trade.oldTiles = *oldTiles; + move.u.trade.newTiles = *newTiles; + + pushEntry( stack, &move ); +} /* stack_addTrade */ + +void +stack_addAssign( StackCtxt* stack, XP_U16 turn, TrayTileSet* tiles ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = ASSIGN_TYPE; + + move.u.assign.tiles = *tiles; + + pushEntry( stack, &move ); +} /* stack_addAssign */ + +static XP_Bool +setCacheReadyFor( StackCtxt* stack, XP_U16 n ) +{ + StackEntry dummy; + XP_U16 i; + + stream_setPos( stack->data, START_OF_STREAM, POS_READ ); + for ( i = 0; i < n; ++i ) { + readEntry( stack, &dummy ); + } + + stack->cacheNext = n; + stack->cachedPos = stream_getPos( stack->data, XP_FALSE ); + + return XP_TRUE; +} /* setCacheReadyFor */ + +XP_U16 +stack_getNEntries( StackCtxt* stack ) +{ + return stack->nEntries; +} /* stack_getNEntries */ + +XP_Bool +stack_getNthEntry( StackCtxt* stack, XP_U16 n, StackEntry* entry ) +{ + XP_Bool found; + + if ( n >= stack->nEntries ) { + found = XP_FALSE; + } else if ( stack->cacheNext != n ) { + XP_ASSERT( !!stack->data ); + found = setCacheReadyFor( stack, n ); + XP_ASSERT( stack->cacheNext == n ); + } else { + found = XP_TRUE; + } + + if ( found ) { + XWStreamPos oldPos = stream_setPos( stack->data, stack->cachedPos, + POS_READ ); + + readEntry( stack, entry ); + entry->moveNum = (XP_U8)n; + + stack->cachedPos = stream_setPos( stack->data, oldPos, POS_READ ); + ++stack->cacheNext; + } + + return found; +} /* stack_getNthEntry */ + +XP_Bool +stack_popEntry( StackCtxt* stack, StackEntry* entry ) +{ + XP_U16 n = stack->nEntries - 1; + XP_Bool found = stack_getNthEntry( stack, n, entry ); + if ( found ) { + stack->nEntries = n; + + setCacheReadyFor( stack, n ); /* set cachedPos by side-effect */ + stack->top = stack->cachedPos; + } + return found; +} /* stack_popEntry */ + +void +stack_redo( StackCtxt* stack ) +{ + if( (stack->nEntries + 1) <= stack->highWaterMark ) { + ++stack->nEntries; + setCacheReadyFor( stack, stack->nEntries ); + stack->top = stack->cachedPos; + } +} /* stack_redo */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/movestak.h b/xwords4/common/movestak.h new file mode 100644 index 000000000..712f80739 --- /dev/null +++ b/xwords4/common/movestak.h @@ -0,0 +1,94 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _MOVESTAK_H_ +#define _MOVESTAK_H_ + +#include "comtypes.h" +#include "model.h" +#include "vtabmgr.h" + +#ifdef CPLUS +extern "C" { +#endif + +enum { ASSIGN_TYPE, MOVE_TYPE, TRADE_TYPE, PHONY_TYPE }; +typedef XP_U8 StackMoveType; + +typedef struct AssignRec { + TrayTileSet tiles; +} AssignRec; + +typedef struct TradeRec { + TrayTileSet oldTiles; + TrayTileSet newTiles; +} TradeRec; + +typedef struct MoveRec { + MoveInfo moveInfo; + TrayTileSet newTiles; +} MoveRec; + +typedef struct PhonyRec { + MoveInfo moveInfo; +} PhonyRec; + +typedef union EntryData { + AssignRec assign; + TradeRec trade; + MoveRec move; + PhonyRec phony; +} EntryData; + +typedef struct StackEntry { + StackMoveType moveType; + XP_U8 playerNum; + XP_U8 moveNum; + EntryData u; +} StackEntry; + +typedef struct StackCtxt StackCtxt; + +StackCtxt* stack_make( MPFORMAL VTableMgr* vtmgr ); +void stack_destroy( StackCtxt* stack ); + +void stack_init( StackCtxt* stack ); + +void stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream ); +void stack_writeToStream( StackCtxt* stack, XWStreamCtxt* stream ); + +void stack_addMove( StackCtxt* stack, XP_U16 turn, MoveInfo* moveInfo, + TrayTileSet* newTiles ); +void stack_addPhony( StackCtxt* stack, XP_U16 turn, MoveInfo* moveInfo ); +void stack_addTrade( StackCtxt* stack, XP_U16 turn, + TrayTileSet* oldTiles, TrayTileSet* newTiles ); +void stack_addAssign( StackCtxt* stack, XP_U16 turn, TrayTileSet* tiles ); + +XP_U16 stack_getNEntries( StackCtxt* stack ); + +XP_Bool stack_getNthEntry( StackCtxt* stack, XP_U16 n, StackEntry* entry ); + +XP_Bool stack_popEntry( StackCtxt* stack, StackEntry* entry ); +void stack_redo( StackCtxt* stack ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c new file mode 100644 index 000000000..2cfc75ba1 --- /dev/null +++ b/xwords4/common/mscore.c @@ -0,0 +1,828 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1998-2001 by Eric House (fixin@peak.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 "modelp.h" +#include "util.h" +#include "engine.h" +#include "game.h" +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define IMPOSSIBLY_LOW_PENALTY (-20*MAX_TRAY_TILES) + +/****************************** prototypes ******************************/ +static XP_Bool isLegalMove( ModelCtxt* model, MoveInfo* moves, XP_Bool silent ); +static XP_U16 word_multiplier( ModelCtxt* model, XP_U16 col, XP_U16 row ); +static XP_U16 find_end( ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool isHorizontal ); +static XP_U16 find_start( ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool isHorizontal ); +static XP_S16 checkScoreMove( ModelCtxt* model, XP_S16 turn, + EngineCtxt* engine, XWStreamCtxt* stream, + XP_Bool silent, WordNotifierInfo* notifyInfo ); +/* static XP_U16 figureWordScore( ModelCtxt* model, MoveInfo* moveInfo, */ +/* EngineCtxt* engine, */ +/* XP_Bool silent, short moveMultiplier, */ +/* WordNotifierInfo* notifyInfo ); */ +static XP_U16 scoreWord( ModelCtxt* model, MoveInfo* movei, + EngineCtxt* engine, XWStreamCtxt* stream, + XP_Bool silent, WordNotifierInfo* notifyInfo ); + +/* for formatting when caller wants an explanation of the score. These live + in separate function called only when stream != NULL so that they'll have + as little impact as possible on the speed when the robot's looking for FAST + scoring */ +typedef struct WordScoreFormatter { + DictionaryCtxt* dict; + XWStreamCtxt* stream; + + XP_UCHAR fullBuf[80]; + XP_U16 bufLen, nTiles; + + XP_Bool firstPass; +} WordScoreFormatter; +static void wordScoreFormatterInit( WordScoreFormatter* fmtr, + XWStreamCtxt* stream, + DictionaryCtxt* dict ); +static void wordScoreFormatterAddTile( WordScoreFormatter* fmtr, Tile tile, + XP_U16 tileMultiplier, + XP_Bool isBlank ); +static void wordScoreFormatterFinish( WordScoreFormatter* fmtr, Tile* word ); +static void formatWordScore( XWStreamCtxt* stream, XP_U16 wordScore, + XP_U16 moveMultiplier ); +static void formatSummary( XWStreamCtxt* stream, ModelCtxt* model, + XP_U16 score ); + + +/* Calculate the score of the current move as it stands. Flag the score + * current so we won't have to do this again until something changes to + * invalidate the score. + */ +static void +scoreCurrentMove( ModelCtxt* model, XP_S16 turn, XWStreamCtxt* stream ) +{ + PlayerCtxt* player = &model->players[turn]; + XP_S16 score; + + XP_ASSERT( !player->curMoveValid ); + + /* recalc goes here */ + score = checkScoreMove( model, turn, (EngineCtxt*)NULL, stream, + XP_TRUE, (WordNotifierInfo*)NULL ); + XP_ASSERT( score >= 0 || score == ILLEGAL_MOVE_SCORE ); + + player->curMoveScore = score; + player->curMoveValid = XP_TRUE; +} /* scoreCurrentMove */ + +void +adjustScoreForUndone( ModelCtxt* model, MoveInfo* mi, XP_U16 turn ) +{ + XP_U16 moveScore; + PlayerCtxt* player = &model->players[turn]; + + if ( mi->nTiles == 0 ) { + moveScore = 0; + } else { + moveScore = figureMoveScore( model, mi, (EngineCtxt*)NULL, + (XWStreamCtxt*)NULL, XP_TRUE, + (WordNotifierInfo*)NULL ); + } + player->score -= moveScore; + player->curMoveScore = 0; + player->curMoveValid = XP_TRUE; +} /* adjustScoreForUndone */ + +XP_Bool +model_checkMoveLegal( ModelCtxt* model, XP_S16 turn, XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo ) +{ + XP_S16 score; + score = checkScoreMove( model, turn, (EngineCtxt*)NULL, stream, XP_FALSE, + notifyInfo ); + return score != ILLEGAL_MOVE_SCORE; +} /* model_checkMoveLegal */ + +void +invalidateScore( ModelCtxt* model, XP_S16 turn ) +{ + model->players[turn].curMoveValid = XP_FALSE; +} /* invalidateScore */ + +XP_Bool +getCurrentMoveScoreIfLegal( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream, XP_S16* score ) +{ + PlayerCtxt* player = &model->players[turn]; + if ( !player->curMoveValid ) { + scoreCurrentMove( model, turn, stream ); + } + + *score = player->curMoveScore; + return player->curMoveScore != ILLEGAL_MOVE_SCORE; +} /* getCurrentMoveScoreIfLegal */ + +XP_S16 +model_getPlayerScore( ModelCtxt* model, XP_S16 player ) +{ + return model->players[player].score; +} /* model_getPlayerScore */ + +/* Based on the current scores based on tiles played and the tiles left in the + * tray, return an array giving the left-over-tile-adjusted scores for each + * player. + */ +void +model_figureFinalScores( ModelCtxt* model, XP_S16* finalScoresP, + XP_S16* tilePenalties ) +{ + XP_S16 i, j; + XP_S16 penalties[MAX_NUM_PLAYERS]; + XP_S16 totalPenalty; + XP_U16 nPlayers = model->nPlayers; + XP_S16 firstDoneIndex = -1; /* not set unless FIRST_DONE_BONUS is set */ + const TrayTileSet* tray; + PlayerCtxt* player; + DictionaryCtxt* dict = model_getDictionary( model ); + CurGameInfo* gi = model->vol.gi; + + XP_MEMSET( finalScoresP, 0, sizeof(*finalScoresP) * MAX_NUM_PLAYERS ); + + totalPenalty = 0; + for ( player = model->players, i = 0; i < nPlayers; ++player, ++i ) { + tray = model_getPlayerTiles( model, i ); + + penalties[i] = 0; + + /* if there are no tiles left and this guy's the first done, make a + note of it in case he's to get a bonus. Note that this assumes + only one player can be out of tiles. */ + if ( (tray->nTiles == 0) && (firstDoneIndex == -1) ) { + firstDoneIndex = i; + } else { + for ( j = tray->nTiles-1; j >= 0; --j ) { + penalties[i] += dict_getTileValue( dict, tray->tiles[j] ); + } + } + + /* include tiles in pending move too for the player whose turn it + is. */ + for ( j = player->nPending - 1; j >= 0; --j ) { + Tile tile = player->pendingTiles[j].tile; + penalties[i] += dict_getTileValue(dict, (Tile)(tile & TILE_VALUE_MASK)); + } + totalPenalty += penalties[i]; + } + + /* now total everybody's scores */ + for ( i = 0; i < nPlayers; ++i ) { + XP_S16 score = model_getPlayerScore( model, i ); + XP_S16 penalty; + + penalty = (i == firstDoneIndex)? totalPenalty: -penalties[i]; + finalScoresP[i] = score + penalty; + + if ( !!tilePenalties ) { + tilePenalties[i] = penalty; + } + + if ( gi->timerEnabled ) { + penalty = player_timePenalty( gi, i ); + finalScoresP[i] -= penalty; + } + } +} /* model_figureFinalScores */ + +/* checkScoreMove. + * Negative score means illegal. + */ +static XP_S16 +checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, + XWStreamCtxt* stream, XP_Bool silent, + WordNotifierInfo* notifyInfo ) +{ + XP_Bool isHorizontal; + XP_S16 score = ILLEGAL_MOVE_SCORE; + PlayerCtxt* player = &model->players[turn]; + + XP_ASSERT( player->nPending <= MAX_TRAY_TILES ); + + if ( player->nPending == 0 ) { + return 0; + } + + if ( tilesInLine( model, turn, &isHorizontal ) ) { + MoveInfo moveInfo; + + normalizeMoves( model, turn, isHorizontal, &moveInfo ); + + if ( isLegalMove( model, &moveInfo, silent ) ) { + score = figureMoveScore( model, &moveInfo, engine, stream, + silent, notifyInfo ); + } + } else if ( !silent ) { /* tiles out of line */ + util_userError( model->vol.util, ERR_TILES_NOT_IN_LINE ); + } + return score; +} /* checkScoreMove */ + +XP_Bool +tilesInLine( ModelCtxt* model, XP_S16 turn, XP_Bool* isHorizontal ) +{ + XP_Bool xIsCommon, yIsCommon; + PlayerCtxt* player = &model->players[turn]; + PendingTile* pt = player->pendingTiles; + XP_U16 commonX = pt->col; + XP_U16 commonY = pt->row; + short i; + + xIsCommon = yIsCommon = XP_TRUE; + + for ( i = 1; ++pt, i < player->nPending; ++i ) { + // test the boolean first in case it's already been made false + // (to save time) + if ( xIsCommon && (pt->col != commonX) ) { + xIsCommon = XP_FALSE; + } + if ( yIsCommon && (pt->row != commonY) ) { + yIsCommon = XP_FALSE; + } + } + *isHorizontal = !xIsCommon; // so will be vertical if both true + return xIsCommon || yIsCommon; +} /* tilesInLine */ + +void +normalizeMoves( ModelCtxt* model, XP_S16 turn, XP_Bool isHorizontal, + MoveInfo* moveInfo ) +{ + XP_S16 lowCol, i, j, thisCol; /* unsigned is a problem on palm */ + PlayerCtxt* player = &model->players[turn]; + XP_U16 nTiles = player->nPending; + XP_S16 lastTaken; + short lowIndex = 0; + PendingTile* pt; + + moveInfo->isHorizontal = isHorizontal; + moveInfo->nTiles = (XP_U8)nTiles; + + lastTaken = -1; + for ( i = 0; i < nTiles; ++i ) { + lowCol = 100; /* high enough to always be changed */ + for ( j = 0; j < nTiles; ++j ) { + pt = &player->pendingTiles[j]; + thisCol = isHorizontal? pt->col:pt->row; + if (thisCol < lowCol && thisCol > lastTaken ) { + lowCol = thisCol; + lowIndex = j; + } + } + /* we've found the next to transfer (4 bytes smaller without a temp + local ptr. */ + pt = &player->pendingTiles[lowIndex]; + moveInfo->tiles[i].varCoord = (XP_U8)(lastTaken = lowCol); + + moveInfo->tiles[i].tile = pt->tile; + } + + pt = &player->pendingTiles[0]; + moveInfo->commonCoord = isHorizontal? pt->row:pt->col; +} /* normalizeMoves */ + +static XP_Bool +modelIsEmptyAt( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + Tile tile; + XP_Bool ignore; + XP_Bool found; + + found = model_getTile( model, col, row, XP_FALSE, -1, &tile, + &ignore, &ignore, (XP_Bool*)NULL ); + return !found; +} /* modelIsEmptyAt */ + +/***************************************************************************** + * Called only after moves have been confirmed to be in the same row, this + * function works whether the word is horizontal or vertical. + * + * For a move to be legal, either of the following must be true: a) + * if there are squares between those added in this move they must be occupied + * by previously placed pieces; or b) if these pieces are contiguous then at + * least one must touch a previously played piece (unless this is the first + * move) NOTE: this function does not verify that a newly placed piece is on an + * empty square. It's assumed that the calling code, most likely that which + * handles dragging the tiles, will have taken care of that. + ****************************************************************************/ +static XP_Bool +isLegalMove( ModelCtxt* model, MoveInfo* mInfo, XP_Bool silent ) +{ + XP_S16 high, low; + XP_S16 col, row; + XP_S16* incr; + XP_S16* commonP; + XP_U16 star_row = model_numRows(model) / 2; + + XP_S16 nTiles = mInfo->nTiles; + MoveInfoTile* moves = mInfo->tiles; + XP_U16 commonCoord = mInfo->commonCoord; + + /* First figure out what the low and high coordinates are in the dimension + not in common */ + low = moves[0].varCoord; + high = moves[nTiles-1].varCoord; + XP_ASSERT( (nTiles == 1) || (low < high) ); + + if ( mInfo->isHorizontal ) { + row = commonCoord; + incr = &col; + commonP = &row; + } else { + col = commonCoord; + incr = &row; + commonP = &col; + } + + /* are we looking at 2a above? */ + if ( (high - low + 1) > nTiles ) { + /* there should be no empty tiles between the ends */ + MoveInfoTile* newTile = moves; /* the newly placed tile to be checked */ + for ( *incr = low; *incr <= high; ++*incr ) { + if ( newTile->varCoord == *incr ) { + ++newTile; + } else if ( modelIsEmptyAt( model, col, row ) ) { + if ( !silent ) { + util_userError( model->vol.util, ERR_NO_EMPTIES_IN_TURN ); + } + return XP_FALSE; + } + } + XP_ASSERT( newTile == &moves[nTiles] ); + return XP_TRUE; + + /* else we're looking at 2b: make sure there's some contact UNLESS + this is the first move */ + } else { + /* check the ends first */ + if ( low != 0 ) { + *incr = low - 1; + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + if ( high != MAX_ROWS-1 ) { + *incr = high+1; + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + /* now the neighbors above... */ + if ( commonCoord != 0 ) { + --*commonP; /* decrement whatever's not being looped over */ + for ( *incr = low; *incr <= high; ++*incr ) { + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + ++*commonP; /* undo the decrement */ + } + /* ...and below */ + if ( commonCoord <= MAX_ROWS - 1 ) { + ++*commonP; + for ( *incr = low; *incr <= high; ++*incr ) { + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + --*commonP; + } + + /* if we got here, it's illegal unless this is the first move -- i.e. + unless one of the tiles is on the STAR */ + if ( ( commonCoord == star_row) && + ( low <= star_row) && ( high >= star_row ) ) { + if ( nTiles > 1 ) { + return XP_TRUE; + } else { + if ( !silent ) { + util_userError(model->vol.util, ERR_TWO_TILES_FIRST_MOVE); + } + return XP_FALSE; + } + } else { + if ( !silent ) { + util_userError( model->vol.util, ERR_TILES_MUST_CONTACT ); + } + return XP_FALSE; + } + } + XP_ASSERT( XP_FALSE ); + return XP_FALSE; /* keep compiler happy */ +} /* isLegalMove */ + +XP_U16 +figureMoveScore( ModelCtxt* model, MoveInfo* moveInfo, EngineCtxt* engine, + XWStreamCtxt* stream, XP_Bool silent, + WordNotifierInfo* notifyInfo ) +{ + XP_U16 col, row; + XP_U16* incr; + XP_U16 oneScore; + XP_U16 score = 0; + short i; + short moveMultiplier = 1; + short multipliers[MAX_TRAY_TILES]; + MoveInfo tmpMI; + MoveInfoTile* tiles; + XP_U16 nTiles = moveInfo->nTiles; + + XP_ASSERT( nTiles > 0 ); + + if ( moveInfo->isHorizontal ) { + row = moveInfo->commonCoord; + incr = &col; + } else { + col = moveInfo->commonCoord; + incr = &row; + } + + for ( i = 0; i < nTiles; ++i ) { + *incr = moveInfo->tiles[i].varCoord; + moveMultiplier *= multipliers[i] = word_multiplier( model, col, row ); + } + + oneScore = scoreWord( model, moveInfo, (EngineCtxt*)NULL, stream, + silent, notifyInfo ); + if ( !!stream ) { + formatWordScore( stream, oneScore, moveMultiplier ); + } + oneScore *= moveMultiplier; + score += oneScore; + + /* set up the invariant slots in tmpMI */ + tmpMI.isHorizontal = !moveInfo->isHorizontal; + tmpMI.nTiles = 1; + tmpMI.tiles[0].varCoord = moveInfo->commonCoord; + + for ( i = 0, tiles = moveInfo->tiles; i < nTiles; ++i, ++tiles ) { + + tmpMI.commonCoord = tiles->varCoord; + tmpMI.tiles[0].tile = tiles->tile; + + oneScore = scoreWord( model, &tmpMI, engine, stream, silent, + notifyInfo ); + if ( !!stream ) { + formatWordScore( stream, oneScore, multipliers[i] ); + } + oneScore *= multipliers[i]; + score += oneScore; + } + + /* did he use all 7 tiles? */ + if ( nTiles == MAX_TRAY_TILES ) { + score += EMPTIED_TRAY_BONUS; + + if ( !!stream ) { + XP_UCHAR* bstr = util_getUserString( model->vol.util, + STR_BONUS_ALL ); + stream_putBytes( stream, bstr, (XP_U8)XP_STRLEN(bstr) ); + } + } + + if ( !!stream ) { + formatSummary( stream, model, score ); + } + + return score; +} /* figureMoveScore */ + +static XP_U16 +word_multiplier( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XWBonusType bonus = util_getSquareBonus( model->vol.util, model, col, row ); + switch ( bonus ) { + case BONUS_DOUBLE_WORD: + return 2; + case BONUS_TRIPLE_WORD: + return 3; + default: + return 1; + } +} /* word_multiplier */ + +static XP_U16 +tile_multiplier( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XWBonusType bonus = util_getSquareBonus( model->vol.util, model, + col, row ); + switch ( bonus ) { + case BONUS_DOUBLE_LETTER: + return 2; + case BONUS_TRIPLE_LETTER: + return 3; + default: + return 1; + } +} /* tile_multiplier */ + +static XP_U16 +scoreWord( ModelCtxt* model, MoveInfo* movei, /* new tiles */ + EngineCtxt* engine,/* for crosswise caching */ + XWStreamCtxt* stream, + XP_Bool silent, /* report error via dialog */ + WordNotifierInfo* notifyInfo ) +{ + XP_U16 tileMultiplier; + XP_U16 restScore, scoreFromCache; + XP_U16 thisTileValue; + XP_U16 nTiles = movei->nTiles; + Tile tile; + XP_U16 start, end; + XP_U16* incr; + XP_U16 col, row; + MoveInfoTile* tiles = movei->tiles; + XP_U16 firstCoord = tiles->varCoord; + DictionaryCtxt* dict = model->vol.dict; + WordScoreFormatter fmtr; + + if ( !!stream ) { + wordScoreFormatterInit( &fmtr, stream, dict ); + } + + if ( movei->isHorizontal ) { + row = movei->commonCoord; + incr = &col; + } else { + col = movei->commonCoord; + incr = &row; + } + + *incr = tiles[nTiles-1].varCoord; + end = find_end( model, col, row, movei->isHorizontal ); + + /* This is the value *incr needs to start with below */ + *incr = tiles[0].varCoord; + start = find_start( model, col, row, movei->isHorizontal ); + + if ( (end - start) < 1 ) { /* one-letter word: score 0 */ + return 0; + } + + if ( IS_BLANK(tiles->tile) ) { + tile = dict_getBlankTile( dict ); + } else { + tile = tiles->tile & TILE_VALUE_MASK; + } + thisTileValue = dict_getTileValue( dict, tile ); + + XP_ASSERT( *incr == tiles[0].varCoord ); + thisTileValue *= tile_multiplier( model, col, row ); + + XP_ASSERT( engine == NULL || nTiles == 1 ); + + if ( engine != NULL ) { + XP_ASSERT( nTiles==1 ); + scoreFromCache = engine_getScoreCache( engine, movei->commonCoord ); + } + + /* for a while, at least, calculate and use the cached crosscheck score + * each time through in the debug case */ + if ( 0 ) { /* makes keeping parens balanced easier */ +#ifdef DEBUG + } else if ( 1 ) { +#else + } else if ( engine == NULL ) { +#endif + Tile checkWordBuf[MAX_ROWS]; + Tile* curTile = checkWordBuf; + + restScore = 0; + for ( *incr = start; *incr <= end; ++*incr ) { + XP_U16 tileScore = 0; + XP_Bool isBlank; + + /* a new move? */ + if ( (nTiles > 0) && (*incr == tiles->varCoord) ) { + tile = tiles->tile & TILE_VALUE_MASK; + isBlank = IS_BLANK(tiles->tile); + /* don't call localGetBlankTile when in silent (robot called) + * mode, as the blank won't be known there. (Assert will + * fail.) */ + + tileMultiplier = tile_multiplier( model, col, row ); + ++tiles; + --nTiles; + } else { /* placed on the board before this move */ + XP_Bool ignore; + tileMultiplier = 1; + + (void)model_getTile( model, col, row, XP_FALSE, -1, &tile, + &isBlank, &ignore, (XP_Bool*)NULL ); + + XP_ASSERT( (tile & TILE_VALUE_MASK) == tile ); + } + + *curTile++ = tile; /* save in case we're checking phonies */ + + if ( !!stream ) { + wordScoreFormatterAddTile( &fmtr, tile, tileMultiplier, + isBlank ); + } + + if ( isBlank ) { + tile = dict_getBlankTile( dict ); + } + tileScore = dict_getTileValue( dict, tile ); + + /* The first tile in the move is already accounted for in + thisTileValue, so skip it here. */ + if ( *incr != firstCoord ) { + restScore += tileScore * tileMultiplier; + } + } /* for each tile */ + + if ( !!notifyInfo ) { + XP_U16 len = curTile - checkWordBuf; + XP_Bool legal = engine_check( dict, checkWordBuf, len ); + + if ( !legal ) { + XP_UCHAR buf[(MAX_ROWS*2)+1]; + dict_tilesToString( dict, checkWordBuf, len, buf ); + (*notifyInfo->proc)( buf, notifyInfo->closure ); + } + } + + if ( !!stream ) { + wordScoreFormatterFinish( &fmtr, checkWordBuf ); + } +#ifdef DEBUG + + } else if ( engine != NULL ) { +#else + } else { /* non-debug case we know it's non-null */ +#endif + XP_ASSERT( nTiles==1 ); + XP_ASSERT( engine_getScoreCache( engine, movei->commonCoord ) + == restScore ); + restScore = engine_getScoreCache( engine, movei->commonCoord ); + } + + return (restScore + thisTileValue); +} /* scoreWord */ + +static XP_U16 +find_start( ModelCtxt* model, XP_U16 col, XP_U16 row, XP_Bool isHorizontal ) +{ + XP_U16* incr = isHorizontal? &col: &row; + + for ( ; ; ) { + if ( *incr == 0 ) { + return 0; + } else { + --*incr; + if ( modelIsEmptyAt( model, col, row ) ) { + return *incr + 1; + } + } + } +} /* find_start */ + +static XP_U16 +find_end( ModelCtxt* model, XP_U16 col, XP_U16 row, XP_Bool isHorizontal ) +{ + XP_U16* incr = isHorizontal? &col: &row; + XP_U16 limit = isHorizontal? MAX_COLS-1:MAX_ROWS-1; + XP_U16 lastGood = *incr; + + XP_ASSERT( col < MAX_COLS ); + XP_ASSERT( row < MAX_ROWS ); + + for ( ; ; ) { + XP_ASSERT( *incr <= limit ); + if ( *incr == limit ) { + return limit; + } else { + ++*incr; + if ( modelIsEmptyAt( model, col, row ) ) { + return lastGood; + } else { + lastGood = *incr; + } + } + } +} /* find_end */ + +static void +wordScoreFormatterInit( WordScoreFormatter* fmtr, XWStreamCtxt* stream, + DictionaryCtxt* dict ) +{ + XP_ASSERT( !!stream ); + fmtr->stream = stream; + fmtr->dict = dict; + + fmtr->bufLen = 0; + fmtr->nTiles = 0; + + fmtr->firstPass = XP_TRUE; +} /* initWordScoreFormatter */ + +static void +wordScoreFormatterAddTile( WordScoreFormatter* fmtr, Tile tile, + XP_U16 tileMultiplier, XP_Bool isBlank ) +{ + XP_UCHAR buf[4]; + XP_UCHAR* fullBufPtr; + XP_UCHAR* prefix; + XP_U16 tileScore; + + ++fmtr->nTiles; + + dict_tilesToString( fmtr->dict, &tile, 1, buf ); + if ( isBlank ) { + tile = dict_getBlankTile( fmtr->dict ); + } + tileScore = dict_getTileValue( fmtr->dict, tile ); + + if ( fmtr->firstPass ) { + prefix = (XP_UCHAR*)" ["; + fmtr->firstPass = XP_FALSE; + } else { + prefix = (XP_UCHAR*)"+"; + } + + fullBufPtr = fmtr->fullBuf + fmtr->bufLen; + fmtr->bufLen += + XP_SNPRINTF( fullBufPtr, + (XP_U16)(sizeof(fmtr->fullBuf) - fmtr->bufLen), + (XP_UCHAR*)(tileMultiplier > 1?"%s(%dx%d)":"%s%d"), + prefix, tileScore, tileMultiplier ); + + XP_ASSERT( XP_STRLEN(fmtr->fullBuf) == fmtr->bufLen ); + XP_ASSERT( fmtr->bufLen < sizeof(fmtr->fullBuf) ); +} /* wordScoreFormatterAddTile */ + +static void +wordScoreFormatterFinish( WordScoreFormatter* fmtr, Tile* word ) +{ + XP_UCHAR buf[(MAX_ROWS*2)+1]; + XP_U16 len = dict_tilesToString( fmtr->dict, word, fmtr->nTiles, buf ); + + stream_putBytes( fmtr->stream, buf, len ); + + stream_putBytes( fmtr->stream, fmtr->fullBuf, fmtr->bufLen ); + stream_putU8( fmtr->stream, ']' ); +} /* wordScoreFormatterFinish */ + +static void +formatWordScore( XWStreamCtxt* stream, XP_U16 wordScore, + XP_U16 moveMultiplier ) +{ + if ( wordScore > 0 ) { + XP_U16 multipliedScore = wordScore * moveMultiplier; + XP_UCHAR tmpBuf[40]; + if ( moveMultiplier > 1 ) { + XP_SNPRINTF( tmpBuf, sizeof(tmpBuf), + (XP_UCHAR*)" => %d x %d = %d" XP_CR, + wordScore, moveMultiplier, multipliedScore ); + } else { + XP_SNPRINTF( tmpBuf, sizeof(tmpBuf), (XP_UCHAR*)" = %d" XP_CR, + multipliedScore ); + } + XP_ASSERT( XP_STRLEN(tmpBuf) < sizeof(tmpBuf) ); + + stream_putBytes( stream, tmpBuf, (XP_U16)XP_STRLEN(tmpBuf) ); + } +} /* formatWordScore */ + +static void +formatSummary( XWStreamCtxt* stream, ModelCtxt* model, XP_U16 score ) +{ + XP_UCHAR buf[60]; + XP_SNPRINTF(buf, sizeof(buf), + util_getUserString(model->vol.util, STRD_TURN_SCORE), + score); + XP_ASSERT( XP_STRLEN(buf) < sizeof(buf) ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN(buf) ); +} /* formatSummary */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/pool.c b/xwords4/common/pool.c new file mode 100644 index 000000000..f40b2abf9 --- /dev/null +++ b/xwords4/common/pool.c @@ -0,0 +1,233 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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 */ + +#include "pool.h" +#include "dictnry.h" +#include "xwstream.h" + +#define pEND 0x70454e44 + +// #define BLANKS_FIRST 1 + +struct PoolContext { + XP_U8* lettersLeft; + XP_U16 numTilesLeft; + XP_U16 numFaces; +#ifdef BLANKS_FIRST + XP_S16 blankIndex; +#endif + MPSLOT +}; + +PoolContext* +pool_make( MPFORMAL_NOCOMMA ) +{ + PoolContext* result = (PoolContext*)XP_MALLOC(mpool, sizeof(*result) ); + + if ( result != NULL ) { + XP_MEMSET( result, 0, sizeof( *result ) ); + MPASSIGN(result->mpool, mpool); + +#ifdef BLANKS_FIRST + result->blankIndex = -1; +#endif + } + + return result; +} /* pool_make */ + +void +pool_writeToStream( PoolContext* pool, XWStreamCtxt* stream ) +{ + stream_putU16( stream, pool->numTilesLeft ); + stream_putU16( stream, pool->numFaces ); + stream_putBytes( stream, pool->lettersLeft, + (XP_U16)(pool->numFaces * sizeof(pool->lettersLeft[0])) ); +#ifdef DEBUG + stream_putU32( stream, pEND ); +#endif +} /* pool_writeToStream */ + +PoolContext* +pool_makeFromStream( MPFORMAL XWStreamCtxt* stream ) +{ + PoolContext* pool = pool_make( MPPARM_NOCOMMA(mpool) ); + + pool->numTilesLeft = stream_getU16( stream ); + pool->numFaces = stream_getU16( stream ); + pool->lettersLeft = (XP_U8*) + XP_MALLOC( mpool, pool->numFaces * sizeof(pool->lettersLeft[0]) ); + stream_getBytes( stream, pool->lettersLeft, + (XP_U16)(pool->numFaces * sizeof(pool->lettersLeft[0])) ); + + XP_ASSERT( stream_getU32( stream ) == pEND ); + + return pool; +} /* pool_makeFromStream */ + +void +pool_destroy( PoolContext* pool ) +{ + XP_ASSERT( pool != NULL ); + XP_FREE( pool->mpool, pool->lettersLeft ); + XP_FREE( pool->mpool, pool ); +} /* pool_destroy */ + +static Tile +getNthPoolTile( PoolContext* pool, short index ) +{ + Tile result; + + /* given an array of counts of remaining letters, subtract each in turn + from the total we seek until that total is at or below zero. The count + that put it (or would put it) under 0 is the one to pick. */ + + if ( 0 ) { +#ifdef BLANKS_FIRST + } else if ( pool->blankIndex >= 0 && pool->lettersLeft[pool->blankIndex] > 0 ) { + result = pool->blankIndex; +#endif + } else { + XP_S16 nextCount = index; + Tile curLetter = 0; + for ( ; ; ) { + nextCount -= pool->lettersLeft[(short)curLetter]; + if ( nextCount < 0 ) { + XP_ASSERT( pool->lettersLeft[(short)curLetter] > 0 ); + result = curLetter; + break; + } else { + ++curLetter; + } + } + } + return result; +} /* getNthPoolTile */ + +static Tile +getRandomTile( PoolContext* pool ) +{ + XP_U16 index = (XP_U16)(XP_RANDOM() % pool->numTilesLeft); + Tile result; + + result = getNthPoolTile( pool, index ); + + --pool->lettersLeft[result]; + --pool->numTilesLeft; + return result; +} /* getRandomTile */ + +void +pool_requestTiles( PoolContext* pool, Tile* tiles, XP_U8* maxNum ) +{ + XP_S16 numWanted = *maxNum; + XP_U16 numWritten = 0; + + XP_ASSERT( numWanted >= 0 ); + + while ( pool->numTilesLeft > 0 && numWanted-- ) { + Tile t = getRandomTile( pool ); + *tiles++ = t; + ++numWritten; + } + *maxNum = (XP_U8)numWritten; +} /* pool_requestTiles */ + +void +pool_replaceTiles( PoolContext* pool, TrayTileSet* tiles ) +{ + XP_U16 nTiles = tiles->nTiles; + Tile* tilesP = tiles->tiles; + + while ( nTiles-- ) { + Tile tile = *tilesP++; /* do I need to filter off high bits? */ + + XP_ASSERT( nTiles < MAX_TRAY_TILES ); + XP_ASSERT( tile < pool->numFaces ); + + ++pool->lettersLeft[tile]; + ++pool->numTilesLeft; + } +} /* pool_replaceTiles */ + +void +pool_removeTiles( PoolContext* pool, TrayTileSet* tiles ) +{ + XP_U16 nTiles = tiles->nTiles; + Tile* tilesP = tiles->tiles; + + XP_ASSERT( nTiles <= MAX_TRAY_TILES ); + + while ( nTiles-- ) { + Tile tile = *tilesP++; /* do I need to filter off high bits? */ + + XP_ASSERT( tile < pool->numFaces ); + XP_ASSERT( pool->lettersLeft[tile] > 0 ); + XP_ASSERT( pool->numTilesLeft > 0 ); + + --pool->lettersLeft[tile]; + --pool->numTilesLeft; + } +} /* pool_removeTiles */ + +XP_U16 +pool_getNTilesLeft( PoolContext* pool ) +{ + return pool->numTilesLeft; +} /* pool_remainingTileCount */ + +XP_U16 +pool_getNTilesLeftFor( PoolContext* pool, Tile tile ) +{ + return pool->lettersLeft[tile]; +} /* pool_remainingTileCount */ + +void +pool_initFromDict( PoolContext* pool, DictionaryCtxt* dict ) +{ + XP_U16 numFaces = dict_numTileFaces( dict ); + Tile i; + + if ( pool->lettersLeft != NULL ) { + XP_FREE( pool->mpool, pool->lettersLeft ); + } + + pool->lettersLeft + = (XP_U8*)XP_MALLOC( pool->mpool, + numFaces * sizeof(pool->lettersLeft[0]) ); + pool->numTilesLeft = 0; + + for ( i = 0; i < numFaces; ++i ) { + XP_U16 numTiles = dict_numTiles( dict, i ); + pool->lettersLeft[i] = (XP_U8)numTiles; + pool->numTilesLeft += numTiles; + } + + pool->numFaces = numFaces; + +#ifdef BLANKS_FIRST + if ( dict_hasBlankTile( dict ) ) { + pool->blankIndex = dict_getBlankTile(dict); + } else { + pool->blankIndex = -1; + } +#endif +} /* pool_initFromDict */ + diff --git a/xwords4/common/pool.h b/xwords4/common/pool.h new file mode 100644 index 000000000..30522a1bf --- /dev/null +++ b/xwords4/common/pool.h @@ -0,0 +1,43 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000-2001 by Eric House (fixin@peak.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. + */ + +#ifndef _POOL_H_ +#define _POOL_H_ + +#include "comtypes.h" +#include "mempool.h" +#include "model.h" + +void pool_requestTiles( PoolContext* pool, Tile* tiles, + /*in out*/ XP_U8* maxNum ); +void pool_replaceTiles( PoolContext* pool, TrayTileSet* tiles ); +void pool_removeTiles( PoolContext* pool, TrayTileSet* tiles ); + +XP_U16 pool_getNTilesLeft( PoolContext* pool ); +XP_U16 pool_getNTilesLeftFor( PoolContext* pool, Tile tile ); + +PoolContext* pool_make( MPFORMAL_NOCOMMA ); + +void pool_destroy( PoolContext* pool ); +void pool_initFromDict( PoolContext* pool, DictionaryCtxt* dict ); + +void pool_writeToStream( PoolContext* pool, XWStreamCtxt* stream ); +PoolContext* pool_makeFromStream( MPFORMAL XWStreamCtxt* stream ); + +#endif diff --git a/xwords4/common/rules.mk b/xwords4/common/rules.mk new file mode 100644 index 000000000..8f064ec1e --- /dev/null +++ b/xwords4/common/rules.mk @@ -0,0 +1,31 @@ +# -*- mode: makefile -*- +# Copyright 2002 by Eric House +# +# 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. + + +all: $(TARGET) + +# Rule for (xplatform) objfiles in this directory +$(COMMONOBJDIR)/%.o: ../common/%.c + ls -d $(COMMONOBJDIR) || mkdir $(COMMONOBJDIR) + $(CC) -c $(CFLAGS) $(INCLUDES) $(DEFINES) -DPLATFORM=$(PLATFORM) \ + $< -o $@ + +# Rule for single-platform objfiles in directory of including Makefile +$(PLATFORM)/%.o: %.c + ls -d $(PLATFORM) || mkdir $(PLATFORM) + $(CC) -c -dD $(CFLAGS) $(INCLUDES) $(DEFINES) -DPLATFORM=$(PLATFORM) $< -o $@ + diff --git a/xwords4/common/server.c b/xwords4/common/server.c new file mode 100644 index 000000000..bfcc2a383 --- /dev/null +++ b/xwords4/common/server.c @@ -0,0 +1,2270 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (fixin@peak.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 */ + +#include "comtypes.h" +#include "server.h" +#include "util.h" +#include "model.h" +#include "comms.h" +#include "memstream.h" +#include "game.h" +/* #include "board.h" */ +#include "states.h" +#include "xwproto.h" +#include "util.h" +#include "pool.h" +#include "engine.h" +#include "strutils.h" + +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define sEND 0x73454e44 + +#define MAX_PASSES 2 /* how many times can all players pass? */ + +#define LOCAL_ADDR NULL + +#define IS_ROBOT(p) ((p)->isRobot) +#define IS_LOCAL(p) ((p)->isLocal) + +enum { + END_REASON_USER_REQUEST, + END_REASON_OUT_OF_TILES, + END_REASON_TOO_MANY_PASSES +}; +typedef XP_U8 GameEndReason; + +typedef struct ServerPlayer { + EngineCtxt* engine; /* each needs his own so don't interfere each other */ + XP_S8 deviceIndex; /* 0 means local, -1 means unknown */ +} ServerPlayer; + +#define UNKNOWN_DEVICE -1 +#define SERVER_DEVICE 0 + +typedef struct RemoteAddress { + XP_PlayerAddr channelNo; +} RemoteAddress; + +/* These are the parts of the server's state that needs to be preserved + across a reset/new game */ +typedef struct ServerVolatiles { + ModelCtxt* model; + CommsCtxt* comms; + XW_UtilCtxt* util; + CurGameInfo* gi; + TurnChangeListener turnChangeListener; + void* turnChangeData; + GameOverListener gameOverListener; + void* gameOverData; + XWStreamCtxt* prevMoveStream; /* save it to print later */ + XW_State stateAfterShow; /* do I need to serialize this? What if + someone quits before I can show the + scores? PENDING(ehouse) */ + XP_Bool showPrevMove; +} ServerVolatiles; + +typedef struct ServerNonvolatiles { + XP_U16 nPassesInRow; + + XP_U8 nDevices; + XW_State gameState; + XP_S8 currentTurn; /* invalid when game is over */ + XP_U8 pendingRegistrations; + XP_Bool showRobotScores; + + RemoteAddress addresses[MAX_NUM_PLAYERS]; + + /* TrayTileSet tradedTiles; */ +} ServerNonvolatiles; + +struct ServerCtxt { + ServerVolatiles vol; + ServerNonvolatiles nv; + + PoolContext* pool; + + BadWordInfo illegalWordInfo; +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 lastMoveSource; +#endif + + ServerPlayer players[MAX_NUM_PLAYERS]; + + MPSLOT +}; + +/******************************* prototypes *******************************/ +static void assignTilesToAll( ServerCtxt* server ); +static void resetEngines( ServerCtxt* server ); +static void nextTurn( ServerCtxt* server, XP_S16 nxtTurn ); + +static void doEndGame( ServerCtxt* server ); +static void endGameInternal( ServerCtxt* server, GameEndReason why ); +static void badWordMoveUndoAndTellUser( ServerCtxt* server, + BadWordInfo* bwi ); + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool handleRegistrationMsg( ServerCtxt* server, + XWStreamCtxt* stream ); +static void registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ); +static void server_sendInitialMessage( ServerCtxt* server ); +static void sendBadWordMsgs( ServerCtxt* server ); +static XP_Bool handleIllegalWord( ServerCtxt* server, + XWStreamCtxt* incomming ); +static void tellMoveWasLegal( ServerCtxt* server ); + +#endif + +#define PICK_NEXT -1 + +/***************************************************************************** + * + ****************************************************************************/ +static void +initServer( ServerCtxt* server ) +{ + XP_U16 i; + CurGameInfo* gi = server->vol.gi; + LocalPlayer* lp; + ServerPlayer* player; + + server->nv.currentTurn = -1; /* game isn't under way yet */ + + if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { + server->nv.gameState = XWSTATE_NONE; + } else { + server->nv.gameState = XWSTATE_BEGIN; + } + + lp = gi->players; + player = server->players; + for ( i = 0; i < gi->nPlayers; ++i, ++lp, ++player ) { + if ( !lp->isLocal/* && !lp->name */ ) { + ++server->nv.pendingRegistrations; + } + + player->deviceIndex = lp->isLocal? SERVER_DEVICE : UNKNOWN_DEVICE; + + } + + server->nv.nDevices = 1; /* local device (0) is always there */ +} /* initServer */ + +ServerCtxt* +server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, XW_UtilCtxt* util ) +{ + ServerCtxt* result = (ServerCtxt*)XP_MALLOC( mpool, sizeof(*result) ); + + if ( result != NULL ) { + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + result->vol.model = model; + result->vol.comms = comms; + result->vol.util = util; + result->vol.gi = util->gameInfo; + + initServer( result ); + } + return result; +} /* server_make */ + +static void +getNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers ) +{ + XP_U16 i; + + nv->nPassesInRow = (XP_U16)stream_getBits( stream, 3 ); + /* number of players is upper limit on device count */ + nv->nDevices = (XP_U8)stream_getBits( stream, PLAYERNUM_NBITS ); + + XP_ASSERT( XWSTATE_GAMEOVER < 1<<4 ); + nv->gameState = (XW_State)stream_getBits( stream, 4 ); + + nv->currentTurn = (XP_S8)stream_getBits( stream, NPLAYERS_NBITS ) - 1; + nv->pendingRegistrations = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); + + for ( i = 0; i < nPlayers; ++i ) { + nv->addresses[i].channelNo = (XP_PlayerAddr)stream_getBits( stream, 16 ); + } +} /* getNV */ + +static void +putNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers ) +{ + XP_U16 i; + + /* Gross hack: with four players nPassesInRow can get bigger than 8. + 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 */ + stream_putBits( stream, PLAYERNUM_NBITS, nv->nDevices ); + + XP_ASSERT( XWSTATE_GAMEOVER < 1<<4 ); + stream_putBits( stream, 4, nv->gameState ); + + /* +1: make -1 (NOTURN) into a positive number */ + stream_putBits( stream, NPLAYERS_NBITS, nv->currentTurn+1 ); + stream_putBits( stream, NPLAYERS_NBITS, nv->pendingRegistrations ); + + for ( i = 0; i < nPlayers; ++i ) { + stream_putBits( stream, 16, nv->addresses[i].channelNo ); + } +} /* putNV */ + +ServerCtxt* +server_makeFromStream( MPFORMAL XWStreamCtxt* stream, ModelCtxt* model, + CommsCtxt* comms, XW_UtilCtxt* util, XP_U16 nPlayers ) +{ + ServerCtxt* server; + short i; + CurGameInfo* gi = util->gameInfo; + + server = server_make( MPPARM(mpool) model, comms, util ); + getNV( stream, &server->nv, nPlayers ); + + if ( stream_getBits(stream, 1) != 0 ) { + server->pool = pool_makeFromStream( MPPARM(mpool) stream ); + } + + for ( i = 0; i < nPlayers; ++i ) { + ServerPlayer* player = &server->players[i]; + + player->deviceIndex = stream_getU8( stream ); + + if ( stream_getU8( stream ) != 0 ) { + LocalPlayer* lp = &gi->players[i]; + player->engine = engine_makeFromStream( MPPARM(mpool) + stream, util, + lp->isRobot ); + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + server->lastMoveSource = (XP_U16)stream_getBits( stream, 2 ); +#endif + + XP_ASSERT( stream_getU32( stream ) == sEND ); + + return server; +} /* server_makeFromStream */ + +void +server_writeToStream( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_U16 i; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + putNV( stream, &server->nv, nPlayers ); + + stream_putBits( stream, 1, server->pool != NULL ); + if ( server->pool != NULL ) { + pool_writeToStream( server->pool, stream ); + } + + for ( i = 0; i < nPlayers; ++i ) { + ServerPlayer* player = &server->players[i]; + + stream_putU8( stream, player->deviceIndex ); + + stream_putU8( stream, (XP_U8)(player->engine != NULL) ); + if ( player->engine != NULL ) { + engine_writeToStream( player->engine, stream ); + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + stream_putBits( stream, 2, server->lastMoveSource ); +#endif + +#ifdef DEBUG + stream_putU32( stream, sEND ); +#endif + +} /* server_writeToStream */ + +static void +cleanupServer( ServerCtxt* server ) +{ + XP_U16 i; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + ServerPlayer* player = &server->players[i]; + if ( player->engine != NULL ) { + engine_destroy( player->engine ); + } + } + XP_MEMSET( server->players, 0, sizeof(server->players) ); + + if ( server->pool != NULL ) { + pool_destroy( server->pool ); + server->pool = (PoolContext*)NULL; + } + + XP_MEMSET( &server->nv, 0, sizeof(server->nv) ); + + if ( !!server->vol.prevMoveStream ) { + stream_destroy( server->vol.prevMoveStream ); + server->vol.prevMoveStream = NULL; + } +} /* cleanupServer */ + +void +server_reset( ServerCtxt* server ) +{ + ServerVolatiles vol = server->vol; + + cleanupServer( server ); + + server->vol = vol; + initServer( server ); +} /* server_reset */ + +void +server_destroy( ServerCtxt* server ) +{ + cleanupServer( server ); + + XP_FREE( server->mpool, server ); +} /* server_destroy */ + +void +server_prefsChanged( ServerCtxt* server, CommonPrefs* cp ) +{ + server->nv.showRobotScores = cp->showRobotScores; +} /* server_prefsChanged */ + +XP_S16 +server_countTilesInPool( ServerCtxt* server ) +{ + XP_S16 result = -1; + PoolContext* pool = server->pool; + if ( !!pool ) { + result = pool_getNTilesLeft( pool ); + } + return result; +} /* server_countTilesInPool */ + +/* I'm a client device. It's my job to start the whole conversation by + * contacting the server and telling him that I exist (and some other stuff, + * including what the players here want to be called.) + */ +#define NAME_LEN_NBITS 6 +#define MAX_NAME_LEN ((1<<(NAME_LEN_NBITS-1))-1) +#ifndef XWFEATURE_STANDALONE_ONLY +void +server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream ) +{ + CurGameInfo* gi = server->vol.gi; + XP_U16 nLocalPlayers; + LocalPlayer* lp; +#ifdef DEBUG + XP_U16 i = 0; +#endif + + XP_ASSERT( gi->serverRole == SERVER_ISCLIENT ); + XP_ASSERT( stream != NULL ); + XP_ASSERT( server->nv.gameState == XWSTATE_NONE ); + + stream_open( stream ); + + stream_putBits( stream, XWPROTO_NBITS, XWPROTO_DEVICE_REGISTRATION ); + + nLocalPlayers = gi->nPlayers; + XP_ASSERT( nLocalPlayers > 0 ); + stream_putBits( stream, NPLAYERS_NBITS, nLocalPlayers ); + + for ( lp = gi->players; nLocalPlayers > 0; ++lp ) { + + XP_ASSERT( i++ < MAX_NUM_PLAYERS ); + + if ( lp->isLocal ) { + + XP_UCHAR* name = emptyStringIfNull(lp->name); + XP_Bool isRobot = lp->isRobot; + XP_U8 len; + stream_putBits( stream, 1, isRobot ); + len = XP_STRLEN(name); + if ( len > MAX_NAME_LEN ) { + len = MAX_NAME_LEN; + } + stream_putBits( stream, NAME_LEN_NBITS, len ); + stream_putBytes( stream, name, len ); + + XP_ASSERT( nLocalPlayers > 0 ); + --nLocalPlayers; + } + } + + stream_destroy( stream ); +} /* server_initClientConnection */ +#endif + +static void +callTurnChangeListener( ServerCtxt* server ) +{ + if ( server->vol.turnChangeListener != NULL ) { + (*server->vol.turnChangeListener)( server->vol.turnChangeData ); + } +} /* callTurnChangeListener */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +handleRegistrationMsg( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_U16 playersInMsg, i; + XP_STATUSF( "handleRegistrationMsg" ); + + /* code will have already been consumed */ + playersInMsg = (XP_U16)stream_getBits( stream, NPLAYERS_NBITS ); + XP_ASSERT( playersInMsg > 0 ); + + if ( server->nv.pendingRegistrations < playersInMsg ) { + util_userError( server->vol.util, ERR_REG_UNEXPECTED_USER ); + return XP_FALSE; + } + + for ( i = 0; i < playersInMsg; ++i ) { + registerRemotePlayer( server, stream ); + + /* This is abusing the semantics of turn change -- at least in the + case where there is another device yet to register -- but we + need to let the board know to redraw the scoreboard with more + players there. */ + callTurnChangeListener( server ); + } + + if ( server->nv.pendingRegistrations == 0 ) { + assignTilesToAll( server ); + server->nv.gameState = XWSTATE_RECEIVED_ALL_REG; + } + return XP_TRUE; +} /* handleRegistrationMsg */ +#endif + +/* Just for grins....trade in all the tiles that weren't used in the + * move the robot manage to make. This is not meant to be strategy, but + * rather to force me to make the trade-communication stuff work well. + */ +#if 0 +static void +robotTradeTiles( ServerCtxt* server, MoveInfo* newMove ) +{ + Tile tradeTiles[MAX_TRAY_TILES]; + XP_S16 turn = server->nv.currentTurn; + Tile* curTiles = model_getPlayerTiles( server->model, turn ); + XP_U16 numInTray = model_getNumPlayerTiles( server->model, turn ); + XP_MEMCPY( tradeTiles, curTiles, numInTray ); + + for ( i = 0; i < numInTray; ++i ) { /* for each tile in tray */ + XP_Bool keep = XP_FALSE; + for ( j = 0; j < newMove->numTiles; ++j ) { /* for each in move */ + Tile movedTile = newMove->tiles[j].tile; + if ( newMove->tiles[j].isBlank ) { + movedTile |= TILE_BLANK_BIT; + } + if ( movedTile == curTiles[i] ) { /* it's in the move */ + keep = XP_TRUE; + break; + } + } + if ( !keep ) { + tradeTiles[numToTrade++] = curTiles[i]; + } + } + + +} /* robotTradeTiles */ +#endif + +#define FUDGE_RANGE 10 +#define MINIMUM_SCORE 5 +static XP_U16 +figureTargetScore( ServerCtxt* server, XP_U16 turn ) +{ + XP_S16 result = 1000; + XP_S16 highScore = 0; + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + XP_U16 i; + + XP_ASSERT( IS_ROBOT(&server->vol.gi->players[turn]) ); + + if ( 1 /* server->nHumanPlayers > 0 */ ) { + result = 0; + + /* find the highest score anybody but the current player has */ + for ( i = 0; i < nPlayers; ++i ) { + if ( i != turn ) { + XP_S16 score = model_getPlayerScore( model, i ); + XP_ASSERT( score >= 0 ); + if ( highScore < score ) { + highScore = score; + } + } + } + + result = (XP_S16)(highScore - model_getPlayerScore( model, turn ) + + (FUDGE_RANGE-(XP_RANDOM() % (FUDGE_RANGE*2)))); + if ( result < 0 ) { + result = MINIMUM_SCORE; + } + } + + return result; +} /* figureTargetScore */ + +static XWStreamCtxt* +mkServerStream( ServerCtxt* server ) +{ + XWStreamCtxt* stream; + stream = mem_stream_make( MPPARM(server->mpool) + util_getVTManager(server->vol.util), + NULL, CHANNEL_NONE, + (MemStreamCloseCallback)NULL ); + XP_ASSERT( !!stream ); + return stream; +} /* mkServerStream */ + +static XP_Bool +makeRobotMove( ServerCtxt* server ) +{ + XP_Bool result = XP_FALSE; + XP_Bool finished; + XP_S16 turn; + const TrayTileSet* tileSet; + MoveInfo newMove; + ModelCtxt* model = server->vol.model; + CurGameInfo* gi = server->vol.gi; + XP_Bool timerEnabled = gi->timerEnabled; + XP_Bool canMove; + XP_U32 time = 0L; /* stupid compiler.... */ + XP_U16 targetScore = NO_SCORE_LIMIT; + XW_UtilCtxt* util = server->vol.util; + + if ( timerEnabled ) { + time = util_getCurSeconds( util ); + } + + turn = server->nv.currentTurn; + XP_ASSERT( turn >= 0 ); + + /* If the player's been recently turned into a robot while he had some + pending tiles on the board we'll have problems. It'd be best to detect + this and put 'em back when that happens. But for now we'll just be + paranoid. PENDING(ehouse) */ + model_resetCurrentTurn( model, turn ); + + tileSet = model_getPlayerTiles( model, turn ); + + if ( gi->robotSmartness == DUMB_ROBOT ) { + targetScore = figureTargetScore( server, turn ); + } + + XP_ASSERT( !!server_getEngineFor( server, turn ) ); + finished = engine_findMove( server_getEngineFor( server, turn ), + model, model_getDictionary( model ), + tileSet->tiles, tileSet->nTiles, + targetScore, &canMove, &newMove ); + if ( finished ) { + XP_UCHAR* str; + XWStreamCtxt* stream = NULL; + + XP_Bool trade = (newMove.nTiles == 0) && canMove && + (server_countTilesInPool( server ) >= MAX_TRAY_TILES); + + server->vol.showPrevMove = XP_TRUE; + if ( server->nv.showRobotScores ) { + stream = mkServerStream( server ); + } + + /* trade if unable to find a move */ + if ( trade ) { + result = server_commitTrade( server, ALLTILES ); + + /* Quick hack to fix gremlin bug where all-robot game seen none + 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 + human players making real moves. */ + ++server->nv.nPassesInRow; + + if ( !!stream ) { + XP_UCHAR tradeBuf[64]; + str = util_getUserString(util, STRD_ROBOT_TRADED); + XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), str, MAX_TRAY_TILES ); + + stream_putBytes( stream, str, (XP_U16)XP_STRLEN(str) ); + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = stream; + } + } else { + /* if canMove is false, this is a fake move, a pass */ + model_makeTurnFromMoveInfo( model, turn, &newMove ); + + if ( !!stream ) { + (void)model_checkMoveLegal( model, turn, stream, NULL ); + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = stream; + } + result = server_commitMove( server ); + } + } + + if ( timerEnabled ) { + gi->players[turn].secondsUsed += + (XP_U16)(util_getCurSeconds( util ) - time); + } else { + XP_ASSERT( gi->players[turn].secondsUsed == 0 ); + } + + return result; /* always return TRUE after robot move? */ +} /* makeRobotMove */ + +static XP_Bool +robotMovePending( ServerCtxt* server ) +{ + XP_S16 turn = server->nv.currentTurn; + if ( turn >= 0 ) { + CurGameInfo* gi = server->vol.gi; + LocalPlayer* player = &gi->players[turn]; + return IS_ROBOT(player) && IS_LOCAL(player); + } + return XP_FALSE; +} /* robotMovePending */ + +static void +showPrevScore( ServerCtxt* server ) +{ + XW_UtilCtxt* util = server->vol.util; + XWStreamCtxt* stream; + XP_UCHAR* str; + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers = gi->nPlayers; + XP_U16 prevTurn; + XP_U16 strCode; + LocalPlayer* lp; + XP_Bool wasRobot; + XP_Bool wasLocal; + + XP_ASSERT( server->nv.showRobotScores ); + + prevTurn = (server->nv.currentTurn + nPlayers - 1) % nPlayers; + lp = &gi->players[prevTurn]; + wasRobot = lp->isRobot; + wasLocal = lp->isLocal; + + if ( wasLocal ) { + XP_ASSERT( wasRobot ); + strCode = STR_ROBOT_MOVED; + } else { + strCode = STR_REMOTE_MOVED; + } + + stream = mkServerStream( server ); + + str = util_getUserString( util, strCode ); + stream_putBytes( stream, str, (XP_U16)XP_STRLEN(str) ); + + if ( !!server->vol.prevMoveStream ) { + XWStreamCtxt* prevStream = server->vol.prevMoveStream; + XP_U16 len = stream_getSize( prevStream ); + XP_UCHAR* buf = XP_MALLOC( server->mpool, len ); + + stream_getBytes( prevStream, buf, len ); + stream_destroy( prevStream ); + server->vol.prevMoveStream = NULL; + + stream_putBytes( stream, buf, len ); + XP_FREE( server->mpool, buf ); + } + + (void)util_userQuery( util, QUERY_ROBOT_MOVE, stream ); + stream_destroy( stream ); + + server->nv.gameState = server->vol.stateAfterShow; +} /* showPrevScore */ + +XP_Bool +server_do( ServerCtxt* server ) +{ + XP_Bool result = XP_TRUE; + XP_Bool moreToDo = XP_FALSE; + + switch( server->nv.gameState ) { + case XWSTATE_BEGIN: + if ( server->nv.pendingRegistrations == 0 ) { /* all players on device */ + assignTilesToAll( server ); + server->nv.gameState = XWSTATE_INTURN; + server->nv.currentTurn = 0; + moreToDo = XP_TRUE; + } + break; + + case XWSTATE_NEEDSEND_BADWORD_INFO: + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER ); + badWordMoveUndoAndTellUser( server, &server->illegalWordInfo ); +#ifndef XWFEATURE_STANDALONE_ONLY + sendBadWordMsgs( server ); +#endif + nextTurn( server, PICK_NEXT ); /* sets server->nv.gameState */ + //moreToDo = XP_TRUE; /* why? */ + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case XWSTATE_RECEIVED_ALL_REG: + server_sendInitialMessage( server ); + /* PENDING isn't INTURN_OFFDEVICE possible too? Or just INTURN? */ + server->nv.gameState = XWSTATE_INTURN; + server->nv.currentTurn = 0; + moreToDo = XP_TRUE; + break; + + case XWSTATE_MOVE_CONFIRM_MUSTSEND: + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER ); + tellMoveWasLegal( server ); + nextTurn( server, PICK_NEXT ); + break; + +#endif /* XWFEATURE_STANDALONE_ONLY */ + + case XWSTATE_NEEDSEND_ENDGAME: + endGameInternal( server, END_REASON_OUT_OF_TILES ); + break; + + case XWSTATE_NEED_SHOWSCORE: + showPrevScore( server ); + moreToDo = XP_TRUE; /* either process turn or end game... */ + break; + case XWSTATE_INTURN: + if ( robotMovePending( server ) ) { + result = makeRobotMove( server ); + /* if robot was interrupted, we need to schedule again */ + moreToDo = !result || robotMovePending( server ); + } + break; + + default: + result = XP_FALSE; + break; + } + if ( moreToDo ) { + util_requestTime( server->vol.util ); + } + return result; +} /* server_do */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_S8 +getIndexForDevice( ServerCtxt* server, XP_PlayerAddr channelNo ) +{ + short i; + XP_S8 result = -1; + + for ( i = 0; i < server->nv.nDevices; ++i ) { + RemoteAddress* addr = &server->nv.addresses[i]; + if ( addr->channelNo == channelNo ) { + result = i; + break; + } + } + + return result; +} /* getIndexForDevice */ + +static LocalPlayer* +findFirstPending( ServerCtxt* server, ServerPlayer** playerP ) +{ + LocalPlayer* lp; + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers = gi->nPlayers; + XP_U16 nPending = server->nv.pendingRegistrations; + + XP_ASSERT( nPlayers > 0 ); + lp = gi->players + nPlayers; + + while ( --lp >= gi->players ) { + --nPlayers; + if ( !lp->isLocal ) { + if ( --nPending == 0 ) { + break; + } + } + } + XP_ASSERT( lp >= gi->players ); /* did we find a slot? */ + *playerP = server->players + nPlayers; + return lp; +} /* findFirstPending */ + +void +registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_S8 deviceIndex; + XP_PlayerAddr channelNo; + XP_UCHAR* name; + XP_U16 nameLen; + LocalPlayer* lp; + ServerPlayer* player = (ServerPlayer*)NULL; + + /* The player must already be there with a null name, or it's an error. + Take the first empty slot. */ + XP_ASSERT( server->nv.pendingRegistrations > 0 ); + + /* find the slot to use */ + lp = findFirstPending( server, &player ); + + /* get data from stream */ + lp->isRobot = stream_getBits( stream, 1 ); + nameLen = stream_getBits( stream, NAME_LEN_NBITS ); + name = (XP_UCHAR*)XP_MALLOC( server->mpool, nameLen + 1 ); + stream_getBytes( stream, name, nameLen ); + name[nameLen] = '\0'; + + replaceStringIfDifferent( MPPARM(server->mpool) &lp->name, name ); + XP_FREE( server->mpool, name ); + + channelNo = stream_getAddress( stream ); + deviceIndex = getIndexForDevice( server, channelNo ); + + --server->nv.pendingRegistrations; + + if ( deviceIndex == -1 ) { + RemoteAddress* addr; + addr = &server->nv.addresses[server->nv.nDevices]; + + deviceIndex = server->nv.nDevices++; + + XP_ASSERT( channelNo != 0 ); + addr->channelNo = channelNo; + } + + player->deviceIndex = deviceIndex; + +} /* registerRemotePlayer */ + +static void +clearLocalRobots( ServerCtxt* server ) +{ + XP_U16 i; + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers = gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + LocalPlayer* player = &gi->players[i]; + if ( IS_LOCAL( player ) ) { + player->isRobot = XP_FALSE; + } + } +} /* clearLocalRobots */ +#endif + +/* Called in response to message from server listing all the names of + * players in the game (in server-assigned order) and their initial + * tray contents. + */ +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +client_readInitialMessage( ServerCtxt* server, XWStreamCtxt* stream ) +{ + DictionaryCtxt* newDict; + DictionaryCtxt* curDict; + XP_U16 nPlayers, nCols; + XP_PlayerAddr channelNo; + short i; + ModelCtxt* model = server->vol.model; + CurGameInfo* gi = server->vol.gi; + CurGameInfo localGI; + XP_U32 gameID; + PoolContext* pool; + + gameID = stream_getBits( stream, 32 ); + XP_STATUSF( "read gameID of %ld; calling comms_setConnID", gameID ); + server->vol.gi->gameID = gameID; + comms_setConnID( server->vol.comms, gameID ); + + XP_MEMSET( &localGI, 0, sizeof(localGI) ); + gi_readFromStream( MPPARM(server->mpool) stream, &localGI ); + localGI.serverRole = SERVER_ISCLIENT; + + /* so it's not lost (HACK!). Without this, a client won't have a default + dict name when a new game is started. */ + localGI.dictName = copyString( MPPARM(server->mpool) gi->dictName ); + gi_copy( MPPARM(server->mpool) gi, &localGI ); + + nCols = localGI.boardSize; + + newDict = util_makeEmptyDict( server->vol.util ); + dict_loadFromStream( newDict, stream ); + + channelNo = stream_getAddress( stream ); + XP_ASSERT( channelNo != 0 ); + XP_ASSERT( server->nv.addresses[0].channelNo == 0 ); + server->nv.addresses[0].channelNo = channelNo; + + /* PENDING init's a bit harsh for setting the size */ + model_init( model, nCols, nCols ); + + nPlayers = localGI.nPlayers; + XP_STATUSF( "reading in %d players", localGI.nPlayers ); + + gi_disposePlayerInfo( MPPARM(server->mpool) &localGI ); + + gi->nPlayers = nPlayers; + model_setNPlayers( model, nPlayers ); + + curDict = model_getDictionary( model ); + + XP_ASSERT( !!newDict ); + + if ( curDict == NULL ) { + model_setDictionary( model, newDict ); + } else if ( dict_tilesAreSame( newDict, curDict ) ) { + /* keep the dict the local user installed */ + dict_destroy( newDict ); + } else { + dict_destroy( curDict ); + model_setDictionary( model, newDict ); + util_userError( server->vol.util, ERR_SERVER_DICT_WINS ); + clearLocalRobots( server ); + } + + XP_ASSERT( !server->pool ); + pool = server->pool = pool_make( MPPARM_NOCOMMA(server->mpool) ); + pool_initFromDict( server->pool, model_getDictionary(model)); + + /* now read the assigned tiles for each player from the stream, and remove + them from the newly-created local pool. */ + for ( i = 0; i < nPlayers; ++i ) { + TrayTileSet tiles; + + traySetFromStream( stream, &tiles ); + XP_ASSERT( tiles.nTiles <= MAX_TRAY_TILES ); + + XP_STATUSF( "got %d tiles for player %d", tiles.nTiles, i ); + + model_assignPlayerTiles( model, i, &tiles ); + + /* remove what the server's assigned so we won't conflict later. */ + pool_removeTiles( pool, &tiles ); + } + + if ( gi->players->isLocal ) { + server->nv.gameState = XWSTATE_INTURN; + } + server->nv.currentTurn = 0; + + /* Give board a chance to redraw self with the full compliment of + known players */ + callTurnChangeListener( server ); + + return XP_TRUE; +} /* client_readInitialMessage */ +#endif + +/* For each remote device, send a message containing the dictionary and the + * names of all the players in the game (including those on the device itself, + * since they might have been changed in the case of conflicts), in the order + * that all must use for the game. Then for each player on the device give + * the starting tray. + */ +#ifndef XWFEATURE_STANDALONE_ONLY + +static void +makeSendableGICopy( ServerCtxt* server, CurGameInfo* giCopy, + XP_U16 deviceIndex ) +{ + XP_U16 nPlayers; + LocalPlayer* clientPl; + XP_U16 i; + XP_MEMCPY( giCopy, server->vol.gi, sizeof(*giCopy) ); + + nPlayers = giCopy->nPlayers; + + for ( clientPl = giCopy->players, i = 0; + i < nPlayers; ++clientPl, ++i ) { + /* adjust isLocal to client's perspective */ + clientPl->isLocal = server->players[i].deviceIndex == deviceIndex; + } + + giCopy->dictName = (XP_UCHAR*)NULL; /* so we don't sent the bytes; Isn't this + a leak? PENDING */ +} /* makeSendableGICopy */ + +static void +server_sendInitialMessage( ServerCtxt* server ) +{ + short i; + XP_U16 deviceIndex; + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + CurGameInfo localGI; + XP_U32 gameID = server->vol.gi->gameID; + + XP_STATUSF( "server_sendInitialMessage" ); + + for ( deviceIndex = 1; deviceIndex < server->nv.nDevices; + ++deviceIndex ) { + RemoteAddress* addr = &server->nv.addresses[deviceIndex]; + XWStreamCtxt* stream = util_makeStreamFromAddr( server->vol.util, + addr->channelNo ); + XP_ASSERT( !!stream ); + stream_open( stream ); + stream_putBits( stream, XWPROTO_NBITS, XWPROTO_CLIENT_SETUP ); + + XP_STATUSF( "putting gameID %ld into msg", gameID ); + stream_putBits( stream, 32, gameID ); + + makeSendableGICopy( server, &localGI, deviceIndex ); + gi_writeToStream( stream, &localGI ); + + dict_writeToStream( model_getDictionary(model), stream ); + + /* send tiles currently in tray */ + for ( i = 0; i < nPlayers; ++i ) { + model_trayToStream( model, i, stream ); + } + + stream_destroy( stream ); + } + + comms_setConnID( server->vol.comms, gameID ); +} /* server_sendInitialMessage */ +#endif + +static void +freeBWI( MPFORMAL BadWordInfo* bwi ) +{ + /* BadWordInfo* bwi = &server->illegalWordInfo; */ + XP_U16 nWords = bwi->nWords; + + while ( nWords-- ) { + XP_FREE( mpool, bwi->words[nWords] ); + bwi->words[nWords] = (XP_UCHAR*)NULL; + } + + bwi->nWords = 0; +} /* freeBWI */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +bwiToStream( XWStreamCtxt* stream, BadWordInfo* bwi ) +{ + XP_U16 nWords = bwi->nWords; + XP_UCHAR** sp; + + stream_putBits( stream, 4, nWords ); + + for ( sp = bwi->words; nWords > 0; --nWords, ++sp ) { + stringToStream( stream, *sp ); + } + +} /* bwiToStream */ + +static void +bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi ) +{ + XP_U16 nWords = stream_getBits( stream, 4 ); + XP_UCHAR** sp = bwi->words; + + bwi->nWords = nWords; + for ( sp = bwi->words; nWords; ++sp, --nWords ) { + *sp = stringFromStream( MPPARM(mpool) stream ); + } +} /* bwiFromStream */ + +#ifdef DEBUG +#define caseStr(var, s) case s: var = #s; break; +static void +printCode(char* intro, XW_Proto code) +{ + char* str = (char*)NULL; + + switch( code ) { + caseStr( str, XWPROTO_ERROR ); + caseStr( str, XWPROTO_CHAT ); + caseStr( str, XWPROTO_DEVICE_REGISTRATION ); + caseStr( str, XWPROTO_CLIENT_SETUP ); + caseStr( str, XWPROTO_MOVEMADE_INFO_CLIENT ); + caseStr( str, XWPROTO_MOVEMADE_INFO_SERVER ); + caseStr( str, XWPROTO_UNDO_INFO_CLIENT ); + caseStr( str, XWPROTO_UNDO_INFO_SERVER ); + caseStr( str, XWPROTO_BADWORD_INFO ); + caseStr( str, XWPROTO_MOVE_CONFIRM ); + caseStr( str, XWPROTO_CLIENT_REQ_END_GAME ); + caseStr( str, XWPROTO_END_GAME ); + } + + XP_STATUSF( "\t%s for %s", intro, str ); +} /* printCode */ +#undef caseStr +#else +#define printCode(intro, code) +#endif + +static XWStreamCtxt* +messageStreamWithHeader( ServerCtxt* server, XP_U16 devIndex, XW_Proto code ) +{ + XWStreamCtxt* stream; + XP_PlayerAddr channelNo = server->nv.addresses[devIndex].channelNo; + + printCode("making", code); + + stream = util_makeStreamFromAddr( server->vol.util, channelNo ); + + stream_open( stream ); + stream_putBits( stream, XWPROTO_NBITS, code ); + + return stream; +} /* messageStreamWithHeader */ + +/* Check that the message belongs to this game, whatever. Pull out the data + * put in by messageStreamWithHeader -- except for the code, which will have + * already come out. + */ +static XP_Bool +readStreamHeader( ServerCtxt* server, XWStreamCtxt* stream ) +{ + return XP_TRUE; +} /* readStreamHeader */ + +static void +sendBadWordMsgs( ServerCtxt* server ) +{ + XWStreamCtxt* stream; + + stream = messageStreamWithHeader( server, server->lastMoveSource, + XWPROTO_BADWORD_INFO ); + stream_putBits( stream, PLAYERNUM_NBITS, server->nv.currentTurn ); + + XP_ASSERT( server->illegalWordInfo.nWords > 0 ); + bwiToStream( stream, &server->illegalWordInfo ); + + stream_destroy( stream ); + + freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); +} /* sendBadWordMsgs */ +#endif + +static void +badWordMoveUndoAndTellUser( ServerCtxt* server, BadWordInfo* bwi ) +{ + XP_U16 turn; + ModelCtxt* model = server->vol.model; + /* I'm the server. I need to send a message to everybody else telling + them the move's rejected. Then undo it on this side, replacing it with + model_commitRejectedPhony(); */ + + model_rejectPreviousMove( model, server->pool, &turn ); + + util_warnIllegalWord( server->vol.util, bwi, turn, XP_TRUE ); +} /* badWordMoveUndoAndTellUser */ + +EngineCtxt* +server_getEngineFor( ServerCtxt* server, XP_U16 playerNum ) +{ + ServerPlayer* player; + EngineCtxt* engine; + + XP_ASSERT( playerNum < server->vol.gi->nPlayers ); + + player = &server->players[playerNum]; + engine = player->engine; + if ( !engine && server->vol.gi->players[playerNum].isLocal ) { + engine = engine_make( MPPARM(server->mpool) + server->vol.util, + server->vol.gi->players[playerNum].isRobot ); + player->engine = engine; + } + + return engine; +} /* server_getEngineFor */ + +void +server_resetEngine( ServerCtxt* server, XP_U16 playerNum ) +{ + ServerPlayer* player = &server->players[playerNum]; + if ( !!player->engine ) { + XP_ASSERT( player->deviceIndex == 0 ); + engine_reset( player->engine ); + } +} /* server_resetEngine */ + +static void +resetEngines( ServerCtxt* server ) +{ + XP_U16 i; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + server_resetEngine( server, i ); + } +} /* resetEngines */ + +#ifdef TEST_ROBOT_TRADE +static void +makeNotAVowel( ServerCtxt* server, Tile* newTile ) +{ + char face[4]; + Tile tile = *newTile; + PoolContext* pool = server->pool; + TrayTileSet set; + DictionaryCtxt* dict = model_getDictionary( server->vol.model ); + XP_U8 numGot = 1; + + set.nTiles = 1; + + for ( ; ; ) { + + XP_U16 len = dict_tilesToString( dict, &tile, 1, face ); + + if ( len == 1 ) { + switch ( face[0] ) { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case '_': + break; + default: + *newTile = tile; + return; + } + } + + set.tiles[0] = tile; + pool_replaceTiles( pool, &set ); + + pool_requestTiles( pool, &tile, &numGot ); + + } + +} /* makeNotAVowel */ +#endif + +static void +assignTilesToAll( ServerCtxt* server ) +{ + XP_U8 numGot = 1; + XP_U16 numAssigned; + short i; + TrayTileSet newTiles[MAX_NUM_PLAYERS]; + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + XP_ASSERT( server->vol.gi->serverRole != SERVER_ISCLIENT ); + XP_ASSERT( model_getDictionary(model) != NULL ); + if ( server->pool == NULL ) { + server->pool = pool_make( MPPARM_NOCOMMA(server->mpool) ); + XP_STATUSF( "initing pool" ); + pool_initFromDict( server->pool, model_getDictionary(model)); + } + + XP_STATUSF( "assignTilesToAll" ); + + XP_MEMSET( &newTiles, 0, sizeof(newTiles) ); + + model_setNPlayers( model, nPlayers ); + + for ( numAssigned = 0; numAssigned < MAX_TRAY_TILES; ++numAssigned ) { + for ( i = 0; i < nPlayers; ++i ) { + + Tile newTile; + XP_U16 index; + + pool_requestTiles( server->pool, &newTile, &numGot ); + +#ifdef TEST_ROBOT_TRADE + /* don't let any of the initial tray tiles be a vowel */ + makeNotAVowel( server, &newTile ); +#endif + + if ( numGot != 1 ) { + goto OUT_OF_TILES; + } + index = newTiles[i].nTiles++; + newTiles[i].tiles[index] = newTile; + } + } + + OUT_OF_TILES: + for ( i = 0; i < nPlayers; ++i ) { + model_assignPlayerTiles( model, i, &newTiles[i] ); + } + + return; +} /* assignTilesToAll */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +getPlayerTime( ServerCtxt* server, XWStreamCtxt* stream, XP_U16 turn ) +{ + CurGameInfo* gi = server->vol.gi; + + if ( gi->timerEnabled ) { + XP_U16 secondsUsed = stream_getU16( stream ); + + gi->players[turn].secondsUsed = secondsUsed; + } +} /* getPlayerTime */ +#endif + +static void +nextTurn( ServerCtxt* server, XP_S16 nxtTurn ) +{ + ServerPlayer* player; + XP_U16 nPlayers = server->vol.gi->nPlayers; + XP_U16 playerTilesLeft; + XP_S16 currentTurn = server->nv.currentTurn; + XP_Bool moreToDo = XP_FALSE; + + if ( nxtTurn == PICK_NEXT ) { + XP_ASSERT( currentTurn >= 0 ); + playerTilesLeft = model_getNumTilesTotal(server->vol.model, + currentTurn); + nxtTurn = (currentTurn+1) % nPlayers; + } else { + /* We're doing an undo, and so won't bother figuring out who the + 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. */ + 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; + } + + server->nv.gameState = XWSTATE_INTURN; /* unless game over */ + + if ( playerTilesLeft > 0 + && (server->nv.nPassesInRow / nPlayers < MAX_PASSES) ) { + + player = &server->players[nxtTurn]; + server->nv.currentTurn = (XP_U8)nxtTurn; + + } else { + /* I discover that the game should end. If I'm the client, though, + should I wait for the server to deduce this and send out a message. + I think so. */ + if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) { + server->nv.gameState = XWSTATE_NEEDSEND_ENDGAME; + moreToDo = XP_TRUE; + } + } + + if ( server->vol.showPrevMove ) { + server->vol.showPrevMove = XP_FALSE; + if ( server->nv.showRobotScores ) { + server->vol.stateAfterShow = server->nv.gameState; + server->nv.gameState = XWSTATE_NEED_SHOWSCORE; + moreToDo = XP_TRUE; + } + } + + /* It's safer, if perhaps not always necessary, to do this here. */ + resetEngines( server ); + + if ( server->nv.gameState != XWSTATE_GAMEOVER ) { + callTurnChangeListener( server ); + } else { + XP_ASSERT(0); + } + + if ( robotMovePending(server) ) { + moreToDo = XP_TRUE; + } + + if ( moreToDo ) { + util_requestTime( server->vol.util ); + } +} /* nextTurn */ + +void +server_setTurnChangeListener( ServerCtxt* server, TurnChangeListener tl, + void* data ) +{ + server->vol.turnChangeListener = tl; + server->vol.turnChangeData = data; +} /* server_setTurnChangeListener */ + +void +server_setGameOverListener( ServerCtxt* server, GameOverListener gol, + void* data ) +{ + server->vol.gameOverListener = gol; + server->vol.gameOverData = data; +} /* server_setTurnChangeListener */ + +static XP_Bool +storeBadWords( XP_UCHAR* word, void* closure ) +{ + ServerCtxt* server = (ServerCtxt*)closure; + + XP_STATUSF( "storeBadWords called with \"%s\"", word ); + + server->illegalWordInfo.words[server->illegalWordInfo.nWords++] + = copyString( MPPARM(server->mpool) word ); + + return XP_TRUE; +} /* storeBadWords */ + +static XP_Bool +checkMoveAllowed( ServerCtxt* server, XP_U16 playerNum ) +{ + CurGameInfo* gi = server->vol.gi; + XP_ASSERT( server->illegalWordInfo.nWords == 0 ); + + if ( gi->phoniesAction == PHONIES_DISALLOW ) { + WordNotifierInfo info; + info.proc = storeBadWords; + info.closure = server; + (void)model_checkMoveLegal( server->vol.model, playerNum, + (XWStreamCtxt*)NULL, &info ); + } + + return server->illegalWordInfo.nWords == 0; +} /* checkMoveAllowed */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +sendMoveTo( ServerCtxt* server, XP_U16 devIndex, XP_U16 turn, + XP_Bool legal, TrayTileSet* newTiles, + TrayTileSet* tradedTiles ) /* null if a move, set if a trade */ +{ + XWStreamCtxt* stream; + XP_Bool isTrade = !!tradedTiles; + CurGameInfo* gi = server->vol.gi; + XW_Proto code = gi->serverRole == SERVER_ISCLIENT? + XWPROTO_MOVEMADE_INFO_CLIENT : XWPROTO_MOVEMADE_INFO_SERVER; + + stream = messageStreamWithHeader( server, devIndex, code ); + + stream_putBits( stream, PLAYERNUM_NBITS, turn ); /* who made the move */ + + traySetToStream( stream, newTiles ); + + stream_putBits( stream, 1, isTrade ); + + if ( isTrade ) { + + traySetToStream( stream, tradedTiles ); + + } else { + stream_putBits( stream, 1, legal ); + + model_currentMoveToStream( server->vol.model, turn, stream ); + + if ( gi->timerEnabled ) { + stream_putU16( stream, gi->players[turn].secondsUsed ); + XP_STATUSF("*** wrote secondsUsed for player %d: %d", + turn, gi->players[turn].secondsUsed ); + } else { + XP_ASSERT( gi->players[turn].secondsUsed == 0 ); + } + + if ( !legal ) { + XP_ASSERT( server->illegalWordInfo.nWords > 0 ); + bwiToStream( stream, &server->illegalWordInfo ); + } + } + + stream_destroy( stream ); +} /* sendMoveTo */ + +static void +readMoveInfo( ServerCtxt* server, XWStreamCtxt* stream, + XP_U16* whoMovedP, XP_Bool* isTradeP, + TrayTileSet* newTiles, TrayTileSet* tradedTiles, + XP_Bool* legalP ) +{ + XP_U16 whoMoved = stream_getBits( stream, PLAYERNUM_NBITS ); + XP_Bool legalMove = XP_TRUE; + XP_Bool isTrade; + + traySetFromStream( stream, newTiles ); + isTrade = stream_getBits( stream, 1 ); + + if ( isTrade ) { + traySetFromStream( stream, tradedTiles ); + } else { + legalMove = stream_getBits( stream, 1 ); + model_makeTurnFromStream( server->vol.model, whoMoved, stream ); + + getPlayerTime( server, stream, whoMoved ); + } + + pool_removeTiles( server->pool, newTiles ); + + *whoMovedP = whoMoved; + *isTradeP = isTrade; + *legalP = legalMove; +} /* readMoveInfo */ + +static void +sendMoveToClientsExcept( ServerCtxt* server, XP_U16 whoMoved, XP_Bool legal, + TrayTileSet* newTiles, TrayTileSet* tradedTiles, + XP_U16 skip ) +{ + XP_U16 devIndex; + + for ( devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) { + if ( devIndex != skip ) { + sendMoveTo( server, devIndex, whoMoved, legal, + newTiles, tradedTiles ); + } + } +} /* sendMoveToClientsExcept */ + +/* Client is reporting a move made, complete with new tiles and time taken by + * the player. Update the model with that information as a tentative move, + * then sent info about it to all the clients, and finally commit the move + * here. + */ +static XP_Bool +reflectMoveAndInform( ServerCtxt* server, XWStreamCtxt* stream ) +{ + ModelCtxt* model = server->vol.model; + XP_U16 whoMoved; + XP_U16 nTilesMoved = 0; /* trade case */ + XP_Bool isTrade; + XP_Bool isLegalMove; + XP_Bool doRequest = XP_FALSE; + TrayTileSet newTiles; + TrayTileSet tradedTiles; + CurGameInfo* gi = server->vol.gi; + XP_U16 sourceClientIndex = + getIndexForDevice( server, stream_getAddress( stream ) ); + XWStreamCtxt* mvStream = NULL; + + XP_ASSERT( gi->serverRole == SERVER_ISSERVER ); + + readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles, + &tradedTiles, &isLegalMove ); + XP_ASSERT( isLegalMove ); /* client should always report as true */ + isLegalMove = XP_TRUE; + + if ( isTrade ) { + + sendMoveToClientsExcept( server, whoMoved, XP_TRUE, &newTiles, + &tradedTiles, sourceClientIndex ); + + model_makeTileTrade( model, whoMoved, + &tradedTiles, &newTiles ); + pool_replaceTiles( server->pool, &tradedTiles ); + + server->vol.showPrevMove = XP_TRUE; + if ( server->nv.showRobotScores ) { + XP_UCHAR tradeBuf[64]; + XP_UCHAR* tradeStr = util_getUserString( server->vol.util, + STRD_ROBOT_TRADED ); + XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), + tradeStr, tradedTiles.nTiles ); + mvStream = mkServerStream( server ); + stream_putBytes( mvStream, tradeBuf, XP_STRLEN(tradeBuf) ); + } + + } 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; + if ( server->nv.showRobotScores ) { + mvStream = mkServerStream( server ); + (void)model_checkMoveLegal( server->vol.model, + server->nv.currentTurn, mvStream, + NULL ); + } + + 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; + server->nv.gameState = XWSTATE_MOVE_CONFIRM_MUSTSEND; + doRequest = XP_TRUE; + } else if ( nTilesLeft > 0 ) { + nextTurn( server, PICK_NEXT ); + } else { + server->nv.gameState = XWSTATE_NEEDSEND_ENDGAME; + doRequest = XP_TRUE; + } + + if ( !!mvStream ) { + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = mvStream; + } + + } 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. */ + server->nv.gameState = XWSTATE_NEEDSEND_BADWORD_INFO; + server->lastMoveSource = sourceClientIndex; + doRequest = XP_TRUE; + } + + if ( doRequest ) { + util_requestTime( server->vol.util ); + } + + return XP_TRUE; +} /* reflectMoveAndInform */ + +static XP_Bool +reflectMove( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_Bool isTrade; + XP_Bool isLegal; + XP_U16 whoMoved; + TrayTileSet newTiles; + TrayTileSet tradedTiles; + ModelCtxt* model = server->vol.model; + + readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles, + &tradedTiles, &isLegal ); + + if ( isTrade ) { + + model_makeTileTrade( model, whoMoved, &tradedTiles, &newTiles ); + pool_replaceTiles( server->pool, &tradedTiles ); + /* pool_removeTiles( server->pool, &newTiles ); */ + server->vol.showPrevMove = XP_TRUE; + } else { + server->vol.showPrevMove = XP_TRUE; + model_commitTurn( model, whoMoved, &newTiles ); + } + + resetEngines( server ); + + if ( !isLegal ) { + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); + handleIllegalWord( server, stream ); + } + + return XP_TRUE; +} /* reflectMove */ +#endif + +/* A local player is done with his turn. If a client device, broadcast + * the move to the server (after which a response should be coming soon.) + * If the server, then that step can be skipped: go straight to doing what + * the server does after learning of a move on a remote device. + * + * Second cut. Whether server or client, be responsible for checking the + * basic legality of the move and taking new tiles out of the pool. If + * client, send the move and new tiles to the server. If the server, fall + * back to what will do after hearing from client: tell everybody who doesn't + * already know what's happened: move and new tiles together. + * + * What about phonies when DISALLOW is set? The server's ultimately + * responsible, since it has the dictionary, so the client can't check. So + * when server, check and send move together with a flag indicating legality. + * Client is responsible for first posting the move to the model and then + * undoing it. When client, send the move and go into a state waiting to hear + * if it was legal -- but only if DISALLOW is set. + */ +XP_Bool +server_commitMove( ServerCtxt* server ) +{ + XP_S16 turn = server->nv.currentTurn; + ModelCtxt* model = server->vol.model; + CurGameInfo* gi = server->vol.gi; + TrayTileSet newTiles; + XP_U16 nTilesMoved; + XP_Bool isLegalMove = XP_TRUE; + XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; + +#ifdef DEBUG + if ( IS_ROBOT( &gi->players[turn] ) ) { + XP_ASSERT( model_checkMoveLegal( model, turn, (XWStreamCtxt*)NULL, + (WordNotifierInfo*)NULL ) ); + } +#endif + + /* commit the move. get new tiles. if server, send to everybody. + if client, send to server. */ + XP_ASSERT( turn >= 0 ); + + nTilesMoved = model_getCurrentMoveCount( model, turn ); + if ( nTilesMoved > 0 ) { + server->nv.nPassesInRow = 0; + } else { + ++server->nv.nPassesInRow; + } + + newTiles.nTiles = (XP_U8)nTilesMoved; + pool_requestTiles( server->pool, newTiles.tiles, &newTiles.nTiles ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( isClient ) { + /* just send to server */ + sendMoveTo( server, SERVER_DEVICE, turn, XP_TRUE, &newTiles, + (TrayTileSet*)NULL ); + } else { + isLegalMove = checkMoveAllowed( server, turn ); + sendMoveToClientsExcept( server, turn, isLegalMove, &newTiles, + (TrayTileSet*)NULL, SERVER_DEVICE ); + } +#else + isLegalMove = checkMoveAllowed( server, turn ); +#endif + + model_commitTurn( model, turn, &newTiles ); + + if ( !isLegalMove && !isClient ) { + badWordMoveUndoAndTellUser( server, &server->illegalWordInfo ); + /* It's ok to free these guys. I'm the server, and the move was made + here, so I've notified all clients already by setting the flag (and + passing the word) in sendMoveToClientsExcept. */ + freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if (isClient && (gi->phoniesAction == PHONIES_DISALLOW) + && nTilesMoved > 0 ) { + server->nv.gameState = XWSTATE_MOVE_CONFIRM_WAIT; + } else { + nextTurn( server, PICK_NEXT ); + } +#else + nextTurn( server, PICK_NEXT ); +#endif + + return XP_TRUE; +} /* server_commitMove */ + +static void +removeTradedTiles( ServerCtxt* server, TileBit selBits, TrayTileSet* tiles ) +{ + XP_U8 nTiles = 0; + XP_S16 index; + XP_S16 turn = server->nv.currentTurn; + + /* selBits: It's gross that server knows this much about tray's + implementation. PENDING(ehouse) */ + + for ( index = 0; selBits != 0; selBits >>= 1, ++index ) { + if ( (selBits & 0x01) != 0 ) { + Tile tile = model_getPlayerTile( server->vol.model, turn, index ); + tiles->tiles[nTiles++] = tile; + } + } + tiles->nTiles = nTiles; +} /* saveTradedTiles */ + +XP_Bool +server_commitTrade( ServerCtxt* server, TileBit selBits ) +{ + TrayTileSet oldTiles; + TrayTileSet newTiles; +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 turn = server->nv.currentTurn; +#endif + + removeTradedTiles( server, selBits, &oldTiles ); + + newTiles.nTiles = oldTiles.nTiles; + pool_requestTiles( server->pool, newTiles.tiles, &newTiles.nTiles ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { + /* just send to server */ + sendMoveTo(server, SERVER_DEVICE, turn, XP_TRUE, &newTiles, &oldTiles); + } else { + sendMoveToClientsExcept( server, turn, XP_TRUE, &newTiles, &oldTiles, + SERVER_DEVICE ); + } +#endif + + pool_replaceTiles( server->pool, &oldTiles ); + model_makeTileTrade( server->vol.model, server->nv.currentTurn, + &oldTiles, &newTiles ); + nextTurn( server, PICK_NEXT ); + return XP_TRUE; +} /* server_commitTrade */ + +XP_S16 +server_getCurrentTurn( ServerCtxt* server ) +{ + return server->nv.currentTurn; +} /* server_getCurrentTurn */ + +XP_Bool +server_getGameIsOver( ServerCtxt* server ) +{ + return server->nv.gameState == XWSTATE_GAMEOVER; +} /* server_getGameIsOver */ + +static void +doEndGame( ServerCtxt* server ) +{ + server->nv.gameState = XWSTATE_GAMEOVER; + server->nv.currentTurn = -1; + + (*server->vol.gameOverListener)( server->vol.gameOverData ); +} /* doEndGame */ + +/* Somebody wants to end the game. + * + * If I'm the server, I send a END_GAME message to all clients. If I'm a + * client, I send the GAME_OVER_REQUEST message to the server. If I'm the + * server and this is called in response to a GAME_OVER_REQUEST, send the + * GAME_OVER message to all clients including the one that requested it. + */ +static void +endGameInternal( ServerCtxt* server, GameEndReason why ) +{ + XP_ASSERT( server->nv.gameState != XWSTATE_GAMEOVER ); + + if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) { + +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 devIndex; + for ( devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) { + XWStreamCtxt* stream; + stream = messageStreamWithHeader( server, devIndex, + XWPROTO_END_GAME ); + stream_destroy( stream ); + } +#endif + doEndGame( server ); + +#ifndef XWFEATURE_STANDALONE_ONLY + } else { + XWStreamCtxt* stream; + stream = messageStreamWithHeader( server, SERVER_DEVICE, + XWPROTO_CLIENT_REQ_END_GAME ); + stream_destroy( stream ); + + /* Do I want to change the state I'm in? I don't think so.... */ +#endif + } +} /* endGameInternal */ + +void +server_endGame( ServerCtxt* server ) +{ + XW_State gameState = server->nv.gameState; + if ( gameState < XWSTATE_GAMEOVER && gameState >= XWSTATE_INTURN ) { + endGameInternal( server, END_REASON_USER_REQUEST ); + } +} /* server_endGame */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +tellMoveWasLegal( ServerCtxt* server ) +{ + XWStreamCtxt* stream; + + stream = messageStreamWithHeader( server, server->lastMoveSource, + XWPROTO_MOVE_CONFIRM ); + stream_destroy( stream ); +} /* tellMoveWasLegal */ + +static XP_Bool +handleIllegalWord( ServerCtxt* server, XWStreamCtxt* incomming ) +{ + BadWordInfo bwi; + XP_U16 whichPlayer; + + whichPlayer = stream_getBits( incomming, PLAYERNUM_NBITS ); + bwiFromStream( MPPARM(server->mpool) incomming, &bwi ); + + badWordMoveUndoAndTellUser( server, &bwi ); + + freeBWI( MPPARM(server->mpool) &bwi ); + + return XP_TRUE; +} /* handleIllegalWord */ + +static XP_Bool +handleMoveOk( ServerCtxt* server, XWStreamCtxt* incomming /* unused */ ) +{ + XP_Bool accepted = XP_TRUE; + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); + XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); + + nextTurn( server, PICK_NEXT ); + + return accepted; +} /* handleMoveOk */ + +static void +sendUndoTo( ServerCtxt* server, XP_U16 devIndex, XP_U16 nUndone, + XP_U16 lastUndone ) +{ + XWStreamCtxt* stream; + CurGameInfo* gi = server->vol.gi; + XW_Proto code = gi->serverRole == SERVER_ISCLIENT? + XWPROTO_UNDO_INFO_CLIENT : XWPROTO_UNDO_INFO_SERVER; + + stream = messageStreamWithHeader( server, devIndex, code ); + + stream_putU16( stream, nUndone ); + stream_putU16( stream, lastUndone ); + + stream_destroy( stream ); +} /* sendUndoTo */ + +static void +sendUndoToClientsExcept( ServerCtxt* server, XP_U16 skip, + XP_U16 nUndone, XP_U16 lastUndone ) +{ + XP_U16 devIndex; + + for ( devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) { + if ( devIndex != skip ) { + sendUndoTo( server, devIndex, nUndone, lastUndone ); + } + } +} /* sendUndoToClientsExcept */ + +static XP_Bool +reflectUndos( ServerCtxt* server, XWStreamCtxt* stream, XW_Proto code ) +{ + XP_U16 nUndone, lastUndone; + XP_S16 moveNum; + XP_U16 turn; + ModelCtxt* model = server->vol.model; + XP_Bool success = XP_TRUE; + + nUndone = stream_getU16( stream ); + lastUndone = stream_getU16( stream ); + + moveNum = lastUndone + nUndone - 1; + + success = model_undoLatestMoves(model, server->pool, nUndone, &turn, + &moveNum); + + if ( success ) { + XP_ASSERT( moveNum == lastUndone ); + + if ( code == XWPROTO_UNDO_INFO_CLIENT ) { /* need to inform */ + XP_U16 sourceClientIndex = getIndexForDevice( + server, stream_getAddress( stream ) ); + + sendUndoToClientsExcept( server, sourceClientIndex, nUndone, + lastUndone ); + + } + nextTurn( server, turn ); + } + + return success; +} /* reflectUndos */ +#endif + +XP_Bool +server_handleUndo( ServerCtxt* server ) +{ + XP_Bool result = XP_FALSE; + XP_U16 lastTurnUndone = 0; /* quiet compiler good */ + XP_U16 nUndone = 0; + ModelCtxt* model; + CurGameInfo* gi; + XP_U16 lastUndone = 0xFFFF; + + model = server->vol.model; + gi = server->vol.gi; + XP_ASSERT( !!model ); + + /* Undo until we find we've just undone a non-robot move. The point is + not to stop with a robot about to act (since that's a bit pointless.) + The exception is that if the first move was a robot move we'll stop + there, and it will immediately move again. */ + for ( ; ; ) { + XP_S16 moveNum = -1; /* don't need it checked */ + if ( !model_undoLatestMoves( model, server->pool, 1, &lastTurnUndone, + &moveNum ) ) { + break; + } + ++nUndone; + XP_ASSERT( moveNum >= 0 ); + lastUndone = moveNum; + if ( !IS_ROBOT(&gi->players[lastTurnUndone]) ) { + break; + } + } + + result = nUndone > 0 ; + if ( result ) { +#ifndef XWFEATURE_STANDALONE_ONLY + XP_ASSERT( lastUndone != 0xFFFF ); + if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { + sendUndoTo( server, SERVER_DEVICE, nUndone, lastUndone ); + } else { + sendUndoToClientsExcept( server, SERVER_DEVICE, nUndone, + lastUndone ); + } +#endif + nextTurn( server, lastTurnUndone ); + } + + return result; +} /* server_handleUndo */ + +#ifndef XWFEATURE_STANDALONE_ONLY +XP_Bool +server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incomming ) +{ + XW_Proto code; + XP_Bool accepted = XP_FALSE; + + code = (XW_Proto)stream_getBits( incomming, XWPROTO_NBITS ); + + printCode("Receiving", code); + + if ( code == XWPROTO_DEVICE_REGISTRATION ) { + /* This message is special: doesn't have the header that's possible + once the game's in progress and communication's been + established. */ + XP_STATUSF( "somebody's registering!!!" ); + accepted = handleRegistrationMsg( server, incomming ); + + } else if ( code == XWPROTO_CLIENT_SETUP ) { + + XP_STATUSF( "client got XWPROTO_CLIENT_SETUP" ); + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); + accepted = client_readInitialMessage( server, incomming ); + + } else if ( readStreamHeader( server, incomming ) ) { + + switch( code ) { + /* case XWPROTO_MOVEMADE_INFO: */ + /* accepted = client_reflectMoveMade( server, incomming ); */ + /* if ( accepted ) { */ + /* nextTurn( server ); */ + /* } */ + /* break; */ + /* case XWPROTO_TRADEMADE_INFO: */ + /* accepted = client_reflectTradeMade( server, incomming ); */ + /* if ( accepted ) { */ + /* nextTurn( server ); */ + /* } */ + /* break; */ + /* case XWPROTO_CLIENT_MOVE_INFO: */ + /* accepted = handleClientMoved( server, incomming ); */ + /* break; */ + /* case XWPROTO_CLIENT_TRADE_INFO: */ + /* accepted = handleClientTraded( server, incomming ); */ + /* break; */ + + case XWPROTO_MOVEMADE_INFO_CLIENT: /* client is reporting a move */ + accepted = reflectMoveAndInform( server, incomming ); + break; + + case XWPROTO_MOVEMADE_INFO_SERVER: /* server telling me about a move */ + accepted = reflectMove( server, incomming ); + if ( accepted ) { + nextTurn( server, PICK_NEXT ); + } + break; + + case XWPROTO_UNDO_INFO_CLIENT: + case XWPROTO_UNDO_INFO_SERVER: + accepted = reflectUndos( server, incomming, code ); + /* nextTurn is called by reflectUndos */ + break; + + case XWPROTO_BADWORD_INFO: + accepted = handleIllegalWord( server, incomming ); + if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) { + nextTurn( server, PICK_NEXT ); + } + break; + + case XWPROTO_MOVE_CONFIRM: + accepted = handleMoveOk( server, incomming ); + break; + + case XWPROTO_CLIENT_REQ_END_GAME: + endGameInternal( server, END_REASON_USER_REQUEST ); + accepted = XP_TRUE; + break; + case XWPROTO_END_GAME: + doEndGame( server ); + accepted = XP_TRUE; + break; + default: + XP_WARNF( "Unknown code on incomming message: %d\n", code ); + break; + } /* switch */ + } + + stream_close( incomming ); + return accepted; +} /* server_receiveMessage */ +#endif + +void +server_formatPoolCounts( ServerCtxt* server, XWStreamCtxt* stream, + XP_U16 nCols ) +{ + DictionaryCtxt* dict; + Tile tile; + XP_U16 nChars; + XP_Bool hasBlank; + Tile blank = 0; /* shut compiler up */ + XP_U16 counts[MAX_UNIQUE_TILES+1]; /* 1 for the blank */ + PoolContext* pool = server->pool; + + if ( !pool ) { + return; /* might want to print an explanation in the stream */ + } + + XP_ASSERT( !!server->vol.model ); + + XP_MEMSET( counts, 0, sizeof(counts) ); + model_countAllTrayTiles( server->vol.model, counts ); + + dict = model_getDictionary( server->vol.model ); + nChars = dict_numTileFaces( dict ); + hasBlank = dict_hasBlankTile( dict ); + if ( hasBlank ) { + blank = dict_getBlankTile( dict ); + } + + for ( tile = 0; ; ) { + XP_UCHAR buf[24]; + XP_UCHAR face[4]; + XP_U16 count, value; + XP_S16 nRemaining; /* signed so assertion will work */ + + dict_tilesToString( dict, &tile, 1, face ); + count = dict_numTiles( dict, tile ); + nRemaining = pool_getNTilesLeftFor( pool, tile ) + counts[tile]; + XP_ASSERT( nRemaining >= 0 ); + value = dict_getTileValue( dict, tile ); + + XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"%s: %d[%d] %d", + face, count, nRemaining, value ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN(buf) ); + + if ( ++tile >= nChars ) { + break; + } else if ( tile % nCols == 0 ) { + stream_putBytes( stream, XP_CR, (XP_U16)XP_STRLEN(XP_CR) ); + } else { + stream_putBytes( stream, (void*)" ", 3 ); + } + } +} /* server_formatPoolCounts */ + +#define IMPOSSIBLY_LOW_SCORE -1000 +void +server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_S16 scores[MAX_NUM_PLAYERS]; + XP_S16 tilePenalties[MAX_NUM_PLAYERS]; + XP_S16 highestIndex; + XP_S16 highestScore; + XP_U16 place, nPlayers, i; + XP_S16 curScore; + ModelCtxt* model = server->vol.model; + XP_UCHAR* addString = util_getUserString( server->vol.util, + STRD_REMAINING_TILES_ADD ); + XP_UCHAR* subString = util_getUserString( server->vol.util, + STRD_UNUSED_TILES_SUB ); + XP_UCHAR timeBuf[16]; + XP_UCHAR* timeStr; + CurGameInfo* gi = server->vol.gi; + + XP_ASSERT( server->nv.gameState == XWSTATE_GAMEOVER ); + + model_figureFinalScores( model, scores, tilePenalties ); + + nPlayers = gi->nPlayers; + + for ( place = 1; ; ++place ) { + XP_UCHAR tmpbuf[48]; + XP_UCHAR buf[128]; + XP_Bool firstDone; + + highestScore = IMPOSSIBLY_LOW_SCORE; + highestIndex = -1; + + for ( i = 0; i < nPlayers; ++i ) { + if ( scores[i] > highestScore ) { + highestIndex = i; + highestScore = scores[i]; + } + } + + if ( highestIndex == -1 ) { + break; /* we're done */ + } else if ( place > 1 ) { + stream_putBytes( stream, XP_CR, (XP_U16)XP_STRLEN(XP_CR) ); + } + scores[highestIndex] = IMPOSSIBLY_LOW_SCORE; + + curScore = model_getPlayerScore( model, highestIndex ); + + timeStr = (XP_UCHAR*)""; + if ( gi->timerEnabled ) { + XP_U16 penalty = player_timePenalty( gi, highestIndex ); + if ( penalty > 0 ) { + XP_SNPRINTF( timeBuf, sizeof(timeBuf), + util_getUserString( + server->vol.util, + STRD_TIME_PENALTY_SUB ), + penalty ); /* positive for formatting */ + timeStr = timeBuf; + } + } + + firstDone = model_getNumTilesTotal( model, highestIndex) == 0; + XP_SNPRINTF( tmpbuf, sizeof(tmpbuf), + (firstDone? addString:subString), + firstDone? + tilePenalties[highestIndex]: + -tilePenalties[highestIndex] ); + + XP_SNPRINTF( buf, sizeof(buf), + (XP_UCHAR*)"[%d] %s: %d" XP_CR " (%d %s%s)", + place, + emptyStringIfNull(gi->players[highestIndex].name), + highestScore, curScore, tmpbuf, timeStr ); + stream_putBytes( stream, buf, (XP_U16)XP_STRLEN(buf) ); + } +} /* server_writeFinalScores */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/server.h b/xwords4/common/server.h new file mode 100644 index 000000000..a9d991d75 --- /dev/null +++ b/xwords4/common/server.h @@ -0,0 +1,124 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include "comtypes.h" /* that's *common* types */ + +#include "commmgr.h" +#include "model.h" + +#ifdef CPLUS +extern "C" { +#endif + +enum { + PHONIES_IGNORE, + PHONIES_WARN, + PHONIES_DISALLOW +}; +typedef XP_U8 XWPhoniesChoice; + +enum { + SERVER_STANDALONE, + SERVER_ISSERVER, + SERVER_ISCLIENT +}; +typedef XP_U8 Connectedness; + +/* typedef struct ServerCtxt ServerCtxt; */ + +/* typedef struct ServerVtable { */ + +/* void (*m_registerPlayer)( ServerCtxt* server, XP_U16 playerNum, */ +/* XP_PlayerSocket socket ); */ + +/* void (*m_getTileValueInfo)( ServerCtxt* server, void* valueBuf ); */ + +/* } ServerVtable; */ + +ServerCtxt* server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, + XW_UtilCtxt* util ); + +ServerCtxt* server_makeFromStream( MPFORMAL XWStreamCtxt* stream, + ModelCtxt* model, CommsCtxt* comms, + XW_UtilCtxt* util, XP_U16 nPlayers ); + +void server_writeToStream( ServerCtxt* server, XWStreamCtxt* stream ); + +void server_reset( ServerCtxt* server ); +void server_destroy( ServerCtxt* server ); + +void server_prefsChanged( ServerCtxt* server, CommonPrefs* cp ); + +typedef void (*TurnChangeListener)( void* data ); +void server_setTurnChangeListener( ServerCtxt* server, TurnChangeListener tl, + void* data ); + +typedef void (*GameOverListener)( void* data ); +void server_setGameOverListener( ServerCtxt* server, GameOverListener gol, + void* data ); + +/* support random assignment by telling creator of new player what it's + * number will be */ +/* XP_U16 server_assignNum( ServerCtxt* server ); */ + +EngineCtxt* server_getEngineFor( ServerCtxt* server, XP_U16 playerNum ); +void server_resetEngine( ServerCtxt* server, XP_U16 playerNum ); + +XP_U16 server_secondsUsedBy( ServerCtxt* server, XP_U16 playerNum ); + +/* It might make more sense to have the board supply the undo method clients + call... */ +XP_Bool server_handleUndo( ServerCtxt* server ); + +/* signed because negative number means nobody's turn yet */ +XP_S16 server_getCurrentTurn( ServerCtxt* server ); +XP_Bool server_getGameIsOver( ServerCtxt* server ); +/* Signed in case no dictionary available */ +XP_S16 server_countTilesInPool( ServerCtxt* server ); + +XP_Bool server_do( ServerCtxt* server ); + +XP_Bool server_commitMove( ServerCtxt* server ); +XP_Bool server_commitTrade( ServerCtxt* server, TileBit bits ); + +/* call this when user wants to end the game */ +void server_endGame( ServerCtxt* server ); + +/* called when running as either client or server */ +XP_Bool server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incomming ); + +/* client-side messages. Client (platform code)owns the stream used to talk + * to the server, and passes it in. */ +#ifndef XWFEATURE_STANDALONE_ONLY +void server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream ); +#endif + +void server_formatPoolCounts( ServerCtxt* server, XWStreamCtxt* stream, + XP_U16 nCols ); +void server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/states.h b/xwords4/common/states.h new file mode 100644 index 000000000..9b7ffd052 --- /dev/null +++ b/xwords4/common/states.h @@ -0,0 +1,61 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _STATES_H_ +#define _STATES_H_ + + + + + +typedef enum { + XWSTATE_NONE, + XWSTATE_BEGIN, + XWSTATE_POOL_INITED, + XWSTATE_NEED_SHOWSCORE, /* client-only */ + XWSTATE_WAITING_ALL_REG, /* includes waiting for dict from server */ + XWSTATE_RECEIVED_ALL_REG, /* includes waiting for dict from server */ + XWSTATE_NEEDSEND_BADWORD_INFO, + XWSTATE_MOVE_CONFIRM_WAIT, /* client's waiting to hear back */ + XWSTATE_MOVE_CONFIRM_MUSTSEND, /* server should tell client asap */ + XWSTATE_NEEDSEND_ENDGAME, + XWSTATE_INTURN, + XWSTATE_GAMEOVER + +} XW_State; + +/* Game starts out in BEGIN. If the server expects other players, it goes + * into XWSTATE_WAITING_ALL_REG. Likewise goes any client waiting to hear + * from the server after sending off its info. A stand-alone game (server) + * goes immediately from BEGIN to WAITING_INFO. + * + * When a device gets tiles for all players (which happens in a single + * message where there's communication involved) it moves to INTURN (either + * ONDEVICE or OFFDEVICE). ONDEVICE changes to WAITING_INFO when the device + * sends its move to the server; OFFDEVICE changes to ONDEVICE if a + * notification that a move's been made is received and it's now a local + * player's turn; otherwise that notification may arrive with no change in + * XW_State (but a change in whose turn it is.) + +After a move is made (current player's device + * sends move + */ + + +#endif diff --git a/xwords4/common/strutils.c b/xwords4/common/strutils.c new file mode 100644 index 000000000..bf99b3bd2 --- /dev/null +++ b/xwords4/common/strutils.c @@ -0,0 +1,165 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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 "strutils.h" +#include "xwstream.h" + +#ifdef CPLUS +extern "C" { +#endif + +XP_U16 +bitsForMax( XP_U32 n ) +{ + XP_U16 result = 0; + XP_ASSERT( n > 0 ); + + while ( n != 0 ) { + n >>= 1; + ++result; + } + + return result; +} /* bitsForMax */ + +static void +tilesToStream( XWStreamCtxt* stream, Tile* tiles, XP_U16 nTiles ) +{ + while ( nTiles-- ) { + stream_putBits( stream, TILE_NBITS, *tiles++ ); + } +} /* tilesToStream */ + +void +traySetToStream( XWStreamCtxt* stream, TrayTileSet* ts ) +{ + XP_U16 nTiles = ts->nTiles; + stream_putBits( stream, NTILES_NBITS, nTiles ); + tilesToStream( stream, ts->tiles, nTiles ); +} /* traySetFromStream */ + +static void +tilesFromStream( XWStreamCtxt* stream, Tile* tiles, XP_U16 nTiles ) +{ + while ( nTiles-- ) { + *tiles++ = (Tile)stream_getBits( stream, TILE_NBITS ); + } +} /* tilesFromStream */ + +void +traySetFromStream( XWStreamCtxt* stream, TrayTileSet* ts ) +{ + XP_U16 nTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS ); + tilesFromStream( stream, ts->tiles, nTiles ); + ts->nTiles = (XP_U8)nTiles; +} /* traySetFromStream */ + +#if 0 +static void +signedToStream( XWStreamCtxt* stream, XP_U16 nBits, XP_S32 num ) +{ + XP_Bool negative = num < 0; + stream_putBits( stream, 1, negative ); + if ( negative ) { + num *= -1; + } + stream_putBits( stream, nBits, num ); +} /* signedToStream */ + +XP_S32 +signedFromStream( XWStreamCtxt* stream, XP_U16 nBits ) +{ + XP_S32 result; + XP_Bool negative = stream_getBits( stream, 1 ); + result = stream_getBits( stream, nBits ); + if ( negative ) { + result *= -1; + } + return result; +} /* signedFromStream */ +#endif + +XP_UCHAR* +stringFromStream( MPFORMAL XWStreamCtxt* stream ) +{ + XP_UCHAR* str = (XP_UCHAR*)NULL; + XP_U16 len = stream_getU8( stream ); + if ( len > 0 ) { + str = (XP_UCHAR*)XP_MALLOC( mpool, len + 1 ); + stream_getBytes( stream, str, len ); + str[len] = '\0'; + } + return str; +} /* makeStringFromStream */ + +void +stringToStream( XWStreamCtxt* stream, XP_UCHAR* str ) +{ + XP_U16 len = str==NULL? 0: XP_STRLEN( (const char*)str ); + XP_ASSERT( len < 0xFF ); + stream_putU8( stream, (XP_U8)len ); + stream_putBytes( stream, str, len ); +} /* putStringToStream */ + +/***************************************************************************** + * + ****************************************************************************/ +XP_UCHAR* +copyString( MPFORMAL XP_UCHAR* instr ) +{ + XP_UCHAR* result = (XP_UCHAR*)NULL; + if ( !!instr ) { + XP_U16 len = XP_STRLEN( (const char*)instr ); + result = (XP_UCHAR*)XP_MALLOC( (MemPoolCtx*)mpool, len + 1 ); + XP_ASSERT( !!result ); + XP_MEMCPY( result, instr, len + 1); + } + return result; +} /* copyString */ + +void +replaceStringIfDifferent( MPFORMAL XP_UCHAR** curLoc, XP_UCHAR* newStr ) +{ + XP_UCHAR* curStr = *curLoc; + + if ( !!newStr && !!curStr && + (0 == XP_STRCMP( (const char*)curStr, (const char*)newStr ) ) ) { + /* do nothing; we're golden */ + } else { + if ( !!curStr ) { + XP_FREE( mpool, curStr ); + } + curStr = copyString( MPPARM(mpool) newStr ); + } + + *curLoc = curStr; +} /* replaceStringIfDifferent */ + +/* + * A wrapper for printing etc. potentially null strings. + */ +XP_UCHAR* +emptyStringIfNull( XP_UCHAR* str ) +{ + return !!str? str : (XP_UCHAR*)""; +} /* emptyStringIfNull */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/strutils.h b/xwords4/common/strutils.h new file mode 100644 index 000000000..0818d7981 --- /dev/null +++ b/xwords4/common/strutils.h @@ -0,0 +1,52 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _STRUTILS_H_ +#define _STRUTILS_H_ + +#include "comtypes.h" +#include "model.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define TILE_NBITS 6 /* 32 tiles plus the blank */ + +XP_U16 bitsForMax( XP_U32 n ); + +void traySetToStream( XWStreamCtxt* stream, TrayTileSet* ts ); +void traySetFromStream( XWStreamCtxt* stream, TrayTileSet* ts ); + +XP_S32 signedFromStream( XWStreamCtxt* stream, XP_U16 nBits ); +void signedToStream( XWStreamCtxt* stream, XP_U16 nBits, XP_S32 num ); + +XP_UCHAR* stringFromStream( MPFORMAL XWStreamCtxt* stream ); +void stringToStream( XWStreamCtxt* stream, XP_UCHAR* str ); + +XP_UCHAR* copyString( MPFORMAL XP_UCHAR* instr ); +void replaceStringIfDifferent( MPFORMAL XP_UCHAR** curLoc, XP_UCHAR* newStr ); + +XP_UCHAR* emptyStringIfNull( XP_UCHAR* str ); + +#ifdef CPLUS +} +#endif + +#endif /* _STRUTILS_H_ */ diff --git a/xwords4/common/tray.c b/xwords4/common/tray.c new file mode 100644 index 000000000..34f9c9930 --- /dev/null +++ b/xwords4/common/tray.c @@ -0,0 +1,659 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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 "boardp.h" +#include "engine.h" + +#ifdef CPLUS +extern "C" { +#endif + +/****************************** prototypes ******************************/ +static XP_Bool startDividerDrag( BoardCtxt* board ); +static XP_Bool startTileDrag( BoardCtxt* board, XP_U8 startIndex ); +static void figureDividerRect( BoardCtxt* board, XP_Rect* rect ); +static void drawPendingScore( BoardCtxt* board ); +static void invalTrayTilesBetween( BoardCtxt* board, XP_U16 tileIndex1, + XP_U16 tileIndex2 ); +static XP_Bool endTileDragIndex( BoardCtxt* board, TileBit last ); + +static XP_S16 +trayLocToIndex( BoardCtxt* board, XP_U16 loc ) +{ + if ( loc >= model_getNumTilesInTray( board->model, + board->selPlayer ) ) { + loc *= -1; + /* (0 * -1) is still 0, so reduce by 1. Will need to adjust + below. NOTE: this is something of a hack.*/ + --loc; + } + return loc; +} /* trayLocToIndex */ + +static XP_S16 +pointToTileIndex( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Bool* onDividerP ) +{ + XP_S16 result = -1; /* not on a tile */ + XP_Rect divider; + XP_Bool onDivider; + + figureDividerRect( board, ÷r ); + onDivider = rectContainsPt( ÷r, x, y ); + + if ( !onDivider ) { + if ( x > divider.left ) { + XP_ASSERT( divider.width == board->dividerWidth ); + x -= divider.width; + } + + XP_ASSERT( x >= board->trayBounds.left ); + x -= board->trayBounds.left; + result = x / board->trayScaleH; + + result = trayLocToIndex( board, result ); + } + + if ( onDividerP != NULL ) { + *onDividerP = onDivider; + } + + return result; +} /* pointToTileIndex */ + +static void +figureTrayTileRect( BoardCtxt* board, XP_U16 index, XP_Rect* rect ) +{ + rect->left = board->trayBounds.left + (index * board->trayScaleH); + rect->top = board->trayBounds.top/* + 1 */; + + rect->width = board->trayScaleH; + rect->height = board->trayScaleV; + + if ( board->dividerLoc[board->selPlayer] <= index ) { + rect->left += board->dividerWidth; + } +} /* figureTileRect */ + +void +drawTray( BoardCtxt* board, XP_Bool focussed ) +{ + DictionaryCtxt* dictionary; + XP_Rect tileRect; + short i; + + if ( (board->trayInvalBits != 0) || board->dividerInvalid ) { + XP_S16 turn = board->selPlayer; + + dictionary = model_getDictionary( board->model ); + + draw_trayBegin( board->draw, &board->trayBounds, turn, + focussed ); + + if ( board->eraseTray ) { + draw_clearRect( board->draw, &board->trayBounds ); + board->eraseTray = XP_FALSE; + } + + if ( (board->trayVisState != TRAY_HIDDEN) && dictionary != NULL ) { + XP_Bool showFaces = board->trayVisState == TRAY_REVEALED; + + if ( turn >= 0 ) { + XP_U16 numInTray = showFaces? + model_getNumTilesInTray( board->model, turn ): + model_getNumTilesTotal( board->model, turn ); + + /* draw in reverse order so drawing happens after erasing */ + for ( i = MAX_TRAY_TILES - 1; i >= 0; --i ) { + + if ( (board->trayInvalBits & (1 << i)) == 0 ) { + continue; + } + + figureTrayTileRect( board, i, &tileRect ); + + if ( i >= numInTray/* && showFace */ ) { + draw_clearRect( board->draw, &tileRect ); + } else if ( showFaces ) { + XP_UCHAR buf[4]; + XP_Bitmap bitmap = NULL; + XP_UCHAR* textP = (XP_UCHAR*)NULL; + XP_U8 flags = board->traySelBits[turn]; + XP_Bool highlighted = (flags & (1<model, + turn, i ); + XP_S16 value; + + if ( dict_faceIsBitmap( dictionary, tile ) ) { + bitmap = dict_getFaceBitmap( dictionary, tile, + XP_TRUE ); + } else { + textP = buf; + dict_tilesToString( dictionary, &tile, 1, textP ); + } + value = dict_getTileValue( dictionary, tile ); + + draw_drawTile( board->draw, &tileRect, textP, bitmap, + value, highlighted ); + } else { + draw_drawTileBack( board->draw, &tileRect ); + } + } + } + + if ( (board->dividerWidth > 0) && board->dividerInvalid ) { + XP_Rect divider; + figureDividerRect( board, ÷r ); + draw_drawTrayDivider( board->draw, ÷r, + board->divDragState.dragInProgress ); + board->dividerInvalid = XP_FALSE; + } + +#ifdef KEYBOARD_NAV + if ( showFaces ) { + TileBit cursorLoc = board->trayCursorLoc[turn]; + if ( !!cursorLoc ) { + XP_U16 index = indexForBits( cursorLoc ); + figureTrayTileRect( board, index, &tileRect ); + draw_drawTrayCursor( board->draw, &tileRect ); + } + } +#endif + } + + draw_trayFinished(board->draw); + + board->trayInvalBits = 0; + } + + drawPendingScore( board ); +} /* drawTray */ + +static void +drawPendingScore( BoardCtxt* board ) +{ + /* Draw the pending score down in the last tray's rect */ + XP_U16 selPlayer = board->selPlayer; + if ( board->trayVisState == TRAY_REVEALED ) { + XP_U16 tilesInTray = model_getNumTilesInTray( board->model, selPlayer ); + if ( tilesInTray < MAX_TRAY_TILES ) { + + XP_S16 turnScore = 0; + XP_Rect lastTileR; + + (void)getCurrentMoveScoreIfLegal( board->model, selPlayer, + (XWStreamCtxt*)NULL, &turnScore ); + figureTrayTileRect( board, MAX_TRAY_TILES-1, &lastTileR ); + draw_score_pendingScore( board->draw, &lastTileR, turnScore, + selPlayer ); + } + } +} /* drawPendingScore */ + +#ifdef DEBUG +static XP_U16 +countSelectedTiles( XP_U8 ti ) +{ + XP_U16 result = 0; + + while ( ti != 0 ) { + ++result; + ti &= ti-1; + } + return result; +} /* countSelectedTiles */ +#endif + +static void +figureDividerRect( BoardCtxt* board, XP_Rect* rect ) +{ + figureTrayTileRect( board, board->dividerLoc[board->selPlayer], rect ); + rect->left -= board->dividerWidth; + rect->width = board->dividerWidth; +} /* figureDividerRect */ + +static XP_Bool +handleTrayDuringTrade( BoardCtxt* board, XP_S16 index ) +{ + TileBit bits; + + XP_ASSERT( index >= 0 ); + + bits = 1 << index; + board->traySelBits[board->selPlayer] ^= bits; + board_invalTrayTiles( board, bits ); + return XP_TRUE; +} /* handleTrayDuringTrade */ + +static XP_Bool +handleActionInTray( BoardCtxt* board, XP_S16 index, XP_Bool onDivider, + XP_Bool waitPenUp ) +{ + XP_Bool result = XP_FALSE; + XP_U16 selPlayer = board->selPlayer; + + if ( onDivider ) { + result = startDividerDrag( board ); + } else if ( board->tradeInProgress[selPlayer] + /* && MY_TURN(board) */ ) { + if ( index >= 0 ) { + result = handleTrayDuringTrade( board, index ); + } + } else if ( index >= 0 ) { + TileBit newIndex = 1 << index; + BoardArrow* arrow = &board->boardArrow[selPlayer]; + + if ( arrow->visible ) { + result = moveTileToArrowLoc( board, (XP_U8)index ); + } else { + XP_U8 selFlags = board->traySelBits[selPlayer]; + /* Tap on selected tile unselects. If we don't do this, + then there's no way to unselect and so no way to turn + off the placement arrow */ + if ( newIndex == selFlags ) { + board_invalTrayTiles( board, selFlags ); + selFlags = NO_TILES; + board->traySelBits[selPlayer] = selFlags; + result = XP_TRUE; + } else { + result = startTileDrag( board, newIndex ); + if ( !waitPenUp ) { + /* key interface means pen up and down happen in the same + event. No dragging. */ + result = endTileDragIndex( board, newIndex ) || result; + } + } + } + } else { /* tap on emptied part of tray */ + if ( index == -(MAX_TRAY_TILES) ) { /* pending score tile */ + result = board_commitTurn( board ); + } else { /* other empty area */ + /* it better be true */ + (void)board_replaceTiles( board ); + result = XP_TRUE; + } + } + return result; +} /* handleActionInTray */ + +XP_Bool +handlePenDownInTray( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool onDivider = XP_FALSE; + XP_S16 index = pointToTileIndex( board, x, y, &onDivider ); + + return handleActionInTray( board, index, onDivider, XP_TRUE ); +} /* handleActionInTray */ + +static XP_Bool +startTileDrag( BoardCtxt* board, TileBit startBit/* , XP_U16 x, XP_U16 y */ ) +{ + XP_Bool result = XP_FALSE; + XP_U16 turn = board->selPlayer; + XP_U8 startSel = board->traySelBits[turn]; + TileDragState* state = &board->tileDragState; + + XP_ASSERT( countSelectedTiles( startBit ) == 1 ); + XP_ASSERT( !state->dragInProgress ); + + state->wasHilited = startSel == startBit; + state->selectionAtStart = startSel; + state->movePending = XP_TRUE; + + state->dragInProgress = XP_TRUE; + XP_STATUSF( "startTileDrag: set dragInProgress\n" ); + state->prevIndex = board->traySelBits[turn] = startBit; + + if ( !state->wasHilited ) { + board_invalTrayTiles( board, (TileBit)(startBit | startSel) ); + result = XP_TRUE; + } + return result; +} /* startTileDrag */ + +static void +moveTileInTray( BoardCtxt* board, TileBit prevTile, TileBit newTile ) +{ + XP_S16 selPlayer = board->selPlayer; + ModelCtxt* model = board->model; + XP_U16 moveTo = indexForBits( prevTile ); + XP_U16 moveFrom = indexForBits( newTile ); + Tile tile; + XP_U16 dividerLoc; + + XP_STATUSF( "moveTileInTray: %d -> %d\n", prevTile, newTile ); + + tile = model_removePlayerTile( model, selPlayer, moveFrom ); + model_addPlayerTile( model, selPlayer, moveTo, tile ); + + dividerLoc = board->dividerLoc[selPlayer]; + if ( moveTo < dividerLoc || moveFrom < dividerLoc ) { + server_resetEngine( board->server, selPlayer ); + } +} /* moveTileInTray */ + +TileBit +continueTileDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + TileDragState* state = &board->tileDragState; + TileBit overTile = 0; + XP_S16 index = pointToTileIndex( board, x, y, (XP_Bool*)NULL ); + + if ( index >= 0 ) { + + overTile = 1 << index; + + if ( overTile != state->prevIndex ) { + + moveTileInTray( board, overTile, state->prevIndex ); + + state->movePending = XP_FALSE; + state->wasHilited = XP_FALSE; // so we won't deselect + state->prevIndex = board->traySelBits[board->selPlayer] = overTile; + } + } + return overTile; +} /* continueTileDrag */ + +XP_U16 +indexForBits( XP_U8 bits ) +{ + XP_U16 result = 0; + XP_U16 mask = 1; + + XP_ASSERT( bits != 0 ); /* otherwise loops forever */ + + while ( (mask & bits) == 0 ) { + ++result; + mask <<= 1; + } + return result; +} /* indexForBits */ + +static XP_Bool +endTileDragIndex( BoardCtxt* board, TileBit last ) +{ + XP_Bool result = XP_FALSE; + XP_U16 selPlayer = board->selPlayer; + + TileDragState* state = &board->tileDragState; + + if ( state->movePending ) { /* no drag took place */ + + if ( state->wasHilited ) { /* if the user just clicked; deselect */ + board_invalTrayTiles( board, state->selectionAtStart ); + board->traySelBits[selPlayer] = NO_TILES; + result = XP_TRUE; + } else if ( (last > 0) + && !board->boardArrow[selPlayer].visible + && (state->selectionAtStart != NO_TILES ) ) { + + if ( model_getCurrentMoveCount( board->model, selPlayer) == 0 ) { + moveTileInTray( board, last, state->selectionAtStart ); + board->traySelBits[selPlayer] = NO_TILES; + } else { + board_invalTrayTiles( + board, + (TileBit)(state->selectionAtStart|last) ); + board->traySelBits[selPlayer] = last; + } + result = XP_TRUE; + } + } else { + board_invalTrayTiles( board, state->prevIndex ); + board->traySelBits[selPlayer] = NO_TILES; + result = XP_TRUE; + } + + state->dragInProgress = XP_FALSE; + XP_STATUSF( "endTileDrag: cleared dragInProgress\n" ); + return result; +} /* endTileDragIndex */ + +XP_Bool +endTileDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + TileBit newTile = continueTileDrag( board, x, y ); + return endTileDragIndex( board, newTile ); +} /* endTileDrag */ + +static XP_Bool +startDividerDrag( BoardCtxt* board ) +{ + board->divDragState.dragInProgress = XP_TRUE; + board->dividerInvalid = XP_TRUE; + return XP_TRUE; +} /* startDividerDrag */ + +static void +dividerMoved( BoardCtxt* board, XP_U8 newLoc ) +{ + XP_U8 oldLoc = board->dividerLoc[board->selPlayer]; + board->dividerLoc[board->selPlayer] = newLoc; + + /* This divider's index corresponds to the tile it's to the left of, and + there's no need to invalidate any tiles to the left of the uppermore + divider position. */ + if ( oldLoc > newLoc ) { + --oldLoc; + } else { + --newLoc; + } + invalTrayTilesBetween( board, newLoc, oldLoc ); + + board->dividerInvalid = XP_TRUE; + /* changed number of available tiles */ + board_resetEngine( board ); +} /* dividerMoved */ + +XP_Bool +continueDividerDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_U8 newOffset; + XP_U16 trayScale = board->trayScaleH; + XP_Bool result = XP_FALSE; + + XP_ASSERT( board->divDragState.dragInProgress ); + + /* Pen might have been dragged out of the tray */ + if ( rectContainsPt( &board->trayBounds, x, y ) ) { + x -= board->trayBounds.left; + newOffset = x / trayScale; + if ( (x % trayScale) > (trayScale/2) ) { + ++newOffset; + } + + result = newOffset != board->dividerLoc[board->selPlayer]; + if ( result ) { + dividerMoved( board, newOffset ); + } + } + return result; +} /* continueDividerDrag */ + +XP_Bool +endDividerDrag( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool result = XP_TRUE; /* b/c hilited state looks different */ + (void)continueDividerDrag( board, x, y ); + board->dividerInvalid = XP_TRUE; + board->divDragState.dragInProgress = XP_FALSE; + return result; +} /* endDividerDrag */ + +void +board_invalTrayTiles( BoardCtxt* board, TileBit what ) +{ + board->trayInvalBits |= what; +} /* invalTrayTiles */ + +static void +invalTrayTilesBetween( BoardCtxt* board, XP_U16 tileIndex1, + XP_U16 tileIndex2 ) +{ + TileBit bits = 0; + + if ( tileIndex1 > tileIndex2 ) { + XP_U16 tmp = tileIndex1; + tileIndex1 = tileIndex2; + tileIndex2 = tmp; + } + + while ( tileIndex1 <= tileIndex2 ) { + bits |= (1 << tileIndex1); + ++tileIndex1; + } + board_invalTrayTiles( board, bits ); +} /* invalTrayTilesBetween */ + +static void +juggleTiles( Tile* tiles, XP_U16 nTiles ) +{ + Tile newTiles[MAX_TRAY_TILES]; + + XP_ASSERT( nTiles <= MAX_TRAY_TILES ); + XP_MEMCPY( newTiles, tiles, nTiles * sizeof(newTiles[0]) ); + + /* Pull out a tile at random, and swap the top tile down into its place so + the array doesn't get sparse. */ + while ( nTiles > 0 ) { + XP_U16 rIndex = ((XP_U16)XP_RANDOM()) % nTiles; + *tiles++ = newTiles[rIndex]; + newTiles[rIndex] = newTiles[--nTiles]; + } + +} /* juggleTiles */ + +XP_Bool +board_juggleTray( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + XP_S16 turn = board->selPlayer; + + + if ( checkRevealTray( board ) ) { + XP_S16 nTiles; + XP_U16 dividerLoc = board->dividerLoc[board->selPlayer]; + ModelCtxt* model = board->model; + + nTiles = model_getNumTilesInTray( model, turn ) - dividerLoc; + if ( nTiles > 1 ) { + XP_S16 i; + Tile tmpT[MAX_TRAY_TILES]; + Tile newT[MAX_TRAY_TILES]; + + /* create unique indices to be juggled; then juggle 'em until + changed. */ + for ( i = 0; i < nTiles; ++i ) { + tmpT[i] = newT[i] = (Tile)i; + } + do { + juggleTiles( newT, nTiles ); + } while ( XP_MEMCMP( newT, tmpT, nTiles * sizeof(newT[0]) ) == 0 ); + + /* save copies of the tiles in juggled order */ + for ( i = 0; i < nTiles; ++i ) { + tmpT[i] = model_getPlayerTile( model, turn, + dividerLoc + newT[i] ); + } + + /* delete tiles off right end; put juggled ones back on the other */ + for ( i = nTiles - 1; i >= 0; --i ) { + (void)model_removePlayerTile( model, turn, -1 ); + model_addPlayerTile( model, turn, dividerLoc, tmpT[i] ); + } + board->traySelBits[turn] = 0; + result = XP_TRUE; + } + } + return result; +} /* board_juggleTray */ + +#ifdef KEYBOARD_NAV +XP_Bool +tray_moveCursor( BoardCtxt* board, XP_Key cursorKey ) +{ + XP_U16 selPlayer = board->selPlayer; + XP_U16 numTrayTiles = model_getNumTilesInTray( board->model, + selPlayer ); + XP_U16 pos; + TileBit newSel; + TileBit oldSel = board->trayCursorLoc[selPlayer]; + + numTrayTiles = MAX_TRAY_TILES; + + if ( oldSel == 0 ) { + if ( cursorKey == XP_CURSOR_KEY_LEFT ) { + newSel = 1 << (numTrayTiles - 1); + } else { + XP_ASSERT( cursorKey == XP_CURSOR_KEY_RIGHT ); + newSel = 1; + } + } else { + pos = indexForBits( oldSel ); + + pos += numTrayTiles; /* add what we'll mod by below: makes circular */ + if ( cursorKey == XP_CURSOR_KEY_LEFT ) { + --pos; + } else if ( cursorKey == XP_CURSOR_KEY_RIGHT ) { + ++pos; + } + + newSel = 1 << (pos % numTrayTiles); + } + board->trayCursorLoc[selPlayer] = newSel; + board_invalTrayTiles( board, newSel | oldSel ); + return XP_TRUE; +} /* tray_moveCursor */ + +XP_Bool +tray_keyAction( BoardCtxt* board ) +{ + TileBit cursor = board->trayCursorLoc[board->selPlayer]; + XP_Bool result; + if ( !!cursor ) { + XP_S16 index = trayLocToIndex( board, indexForBits( cursor ) ); + result = handleActionInTray( board, index, XP_FALSE, XP_FALSE ); + } else { + result = XP_FALSE; + } + + return result; +} /* tray_selectCurTile */ +#endif + +#if defined FOR_GREMLINS || defined KEYBOARD_NAV +XP_Bool +board_moveDivider( BoardCtxt* board, XP_Bool right ) +{ + XP_Bool result = board->trayVisState == TRAY_REVEALED; + if ( result ) { + XP_U8 loc = board->dividerLoc[board->selPlayer]; + loc += MAX_TRAY_TILES + 1; + loc += right? 1:-1; + loc %= MAX_TRAY_TILES + 1; + + dividerMoved( board, loc ); + } + return result; +} /* dividerMovedOne */ +#endif + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/util.h b/xwords4/common/util.h new file mode 100644 index 000000000..cce5f17de --- /dev/null +++ b/xwords4/common/util.h @@ -0,0 +1,199 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include "comtypes.h" + +#include "dawg.h" +#include "model.h" +#include "board.h" +#include "mempool.h" +#include "vtabmgr.h" + +#define LETTER_NONE '\0' + +typedef enum { + ERR_TILES_NOT_IN_LINE, /* scoring a move where tiles aren't in line */ + ERR_NO_EMPTIES_IN_TURN, + ERR_TWO_TILES_FIRST_MOVE, + ERR_TILES_MUST_CONTACT, +/* ERR_NO_HINT_MID_TURN, */ + ERR_TOO_FEW_TILES_LEFT_TO_TRADE, + ERR_NOT_YOUR_TURN, + ERR_NO_PEEK_ROBOT_TILES, +#ifndef XWFEATURE_STANDALONE_ONLY + ERR_SERVER_DICT_WINS, + ERR_NO_PEEK_REMOTE_TILES, + ERR_REG_UNEXPECTED_USER, /* server asked to register too many remote + users */ +#endif + ERR_CANT_TRADE_MID_MOVE, + ERR_CANT_ENGINE_MID_MOVE +/* ERR_NOT_YOUR_TURN_TO_TRADE, */ +/* ERR_NOT_YOUR_TURN_TO_MOVE, */ +} UtilErrID; + +typedef enum { + QUERY_COMMIT_TURN, /* 0 means cancel; 1 means commit */ + QUERY_COMMIT_TRADE, + QUERY_ROBOT_MOVE, + QUERY_ROBOT_TRADE +} UtilQueryID; + +typedef struct BadWordInfo { + XP_U16 nWords; + XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */ +} BadWordInfo; + +/* Platform-specific utility functions that need to be + */ +typedef struct UtilVtable { + + VTableMgr* (*m_util_getVTManager)(XW_UtilCtxt* uc); + + XWStreamCtxt* (*m_util_makeStreamFromAddr )(XW_UtilCtxt* uc, + XP_U16 channelNo ); + + XWBonusType (*m_util_getSquareBonus)( XW_UtilCtxt* uc, ModelCtxt* model, + XP_U16 col, XP_U16 row ); + void (*m_util_userError)( XW_UtilCtxt* uc, UtilErrID id ); + + XP_U16 (*m_util_userQuery)( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ); + + void (*m_util_askBlankFace)( XW_UtilCtxt* uc, DictionaryCtxt* dict, + XP_UCHAR* buf ); + XP_Bool (*m_util_askPassword)( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ); + + void (*m_util_trayHiddenChange)(XW_UtilCtxt* uc, + XW_TrayVisState newState ); + void (*m_util_yOffsetChange)(XW_UtilCtxt* uc, XP_U16 oldOffset, + XP_U16 newOffset ); + + void (*m_util_notifyGameOver)( XW_UtilCtxt* uc ); + + XP_Bool (*m_util_hiliteCell)( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ); + + XP_Bool (*m_util_engineProgressCallback)( XW_UtilCtxt* uc ); + + void (*m_util_setTimer)( XW_UtilCtxt* uc, XWTimerReason why ); + + void (*m_util_requestTime)( XW_UtilCtxt* uc ); + + XP_U32 (*m_util_getCurSeconds)( XW_UtilCtxt* uc ); + + DictionaryCtxt* (*m_util_makeEmptyDict)( XW_UtilCtxt* uc ); + + XP_UCHAR* (*m_util_getUserString)( XW_UtilCtxt* uc, XP_U16 stringCode ); + + XP_Bool (*m_util_warnIllegalWord)( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ); +#ifdef BEYOND_IR + void (*m_util_listenPortChange)( XW_UtilCtxt* uc, XP_U16 listenPort ); +#endif + +#ifdef SHOW_PROGRESS + void (*m_util_engineStarting)( XW_UtilCtxt* uc ); + void (*m_util_engineStopping)( XW_UtilCtxt* uc ); +#endif +} UtilVtable; + + +struct XW_UtilCtxt { + UtilVtable* vtable; + + struct CurGameInfo* gameInfo; + + void* closure; + MPSLOT +}; + +#define util_getVTManager(uc) \ + (uc)->vtable->m_util_getVTManager((uc)) + +#define util_makeStreamFromAddr(uc,a) \ + (uc)->vtable->m_util_makeStreamFromAddr((uc),(a)) + +#define util_getSquareBonus(uc,m,c,r) \ + (uc)->vtable->m_util_getSquareBonus((uc),(m),(c),(r)) + +#define util_userError(uc,err) \ + (uc)->vtable->m_util_userError((uc),(err)) + +#define util_userQuery(uc,qcode,str) \ + (uc)->vtable->m_util_userQuery((uc),(qcode),(str)) + +#define util_askBlankFace( uc, d, b ) \ + (uc)->vtable->m_util_askBlankFace((uc), (d), (b) ) + +#define util_askPassword( uc, n, b, lp ) \ + (uc)->vtable->m_util_askPassword( (uc), (n), (b), (lp) ) + +#define util_trayHiddenChange( uc, b ) \ + (uc)->vtable->m_util_trayHiddenChange((uc), (b)) + +#define util_yOffsetChange( uc, o, n ) \ + (uc)->vtable->m_util_yOffsetChange((uc), (o), (n) ) + +#define util_notifyGameOver( uc ) \ + (uc)->vtable->m_util_notifyGameOver((uc)) + +#define util_hiliteCell( uc, c, r ) \ + (uc)->vtable->m_util_hiliteCell((uc), (c), (r)) + +#define util_engineProgressCallback( uc ) \ + (uc)->vtable->m_util_engineProgressCallback((uc)) + +#define util_setTimer( uc, why ) \ + (uc)->vtable->m_util_setTimer((uc),(why)) + +#define util_requestTime( uc ) \ + (uc)->vtable->m_util_requestTime((uc)) + +#define util_getCurSeconds(uc) \ + (uc)->vtable->m_util_getCurSeconds((uc)) + +#define util_makeEmptyDict( uc ) \ + (uc)->vtable->m_util_makeEmptyDict((uc)) + +#define util_getUserString( uc, c ) \ + (uc)->vtable->m_util_getUserString((uc),(c)) + +#define util_warnIllegalWord( uc, w, p, b ) \ + (uc)->vtable->m_util_warnIllegalWord((uc),(w),(p),(b)) + +#ifdef BEYOND_IR +#define util_listenPortChange( uc, port ) \ + (uc)->vtable->m_util_listenPortChange((uc), (port)) +#endif + +# ifdef SHOW_PROGRESS +# define util_engineStarting( uc ) \ + (uc)->vtable->m_util_engineStarting((uc)) +# define util_engineStopping( uc ) \ + (uc)->vtable->m_util_engineStopping((uc)) +# else +# define util_engineStarting( uc ) +# define util_engineStopping( uc ) +# endif + +#endif diff --git a/xwords4/common/virtuals.h b/xwords4/common/virtuals.h new file mode 100644 index 000000000..28c7e0fd9 --- /dev/null +++ b/xwords4/common/virtuals.h @@ -0,0 +1,33 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _VIRTUALS_H_ +#define _VIRTUALS_H_ + +/* List of classes requiring vtables -- for allocating and keeping track of + vtables in some central location. */ +enum { + VIRTUAL_UTIL, + VIRTUAL_DRAW, + VIRTUAL_STREAM, + VIRTUAL_NUM_VIRTUALS /* must be last */ +} XW_VIRTUALS; + + +#endif diff --git a/xwords4/common/vtabmgr.c b/xwords4/common/vtabmgr.c new file mode 100644 index 000000000..0908c04b4 --- /dev/null +++ b/xwords4/common/vtabmgr.c @@ -0,0 +1,75 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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 "vtabmgr.h" + +#define VTABLE_NUM_SLOTS VTABLE_LAST_ENTRY + +#ifdef CPLUS +extern "C" { +#endif + +struct VTableMgr { + void* slots[VTABLE_NUM_SLOTS]; +}; + +VTableMgr* +make_vtablemgr( MPFORMAL_NOCOMMA ) +{ + VTableMgr* result = (VTableMgr*)XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + return result; +} /* make_vtablemgr */ + +void +vtmgr_destroy( MPFORMAL VTableMgr* vtmgr ) +{ + XP_U16 i; + + XP_ASSERT( !!vtmgr ); + + for ( i = 0; i < VTABLE_NUM_SLOTS; ++i ) { + void* vtable = vtmgr->slots[i]; + if ( !!vtable ) { + XP_FREE( mpool, vtable ); + } + } + + XP_FREE( mpool, vtmgr ); +} /* vtmgr_destroy */ + +void +vtmgr_setVTable( VTableMgr* vtmgr, VtableType typ, void* vtable ) +{ + XP_ASSERT( typ < VTABLE_NUM_SLOTS ); + XP_ASSERT( !vtmgr->slots[typ] ); + vtmgr->slots[typ] = vtable; +} /* VTMSetVtable */ + +void* +vtmgr_getVTable( VTableMgr* vtmgr, VtableType typ ) +{ + XP_ASSERT( typ < VTABLE_NUM_SLOTS ); + return vtmgr->slots[typ]; +} /* VTMGetVtable */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/vtabmgr.h b/xwords4/common/vtabmgr.h new file mode 100644 index 000000000..337a6e62e --- /dev/null +++ b/xwords4/common/vtabmgr.h @@ -0,0 +1,50 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _VTABMGR_H_ +#define _VTABMGR_H_ + +#include "comtypes.h" +#include "mempool.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef enum { + VTABLE_MEM_STREAM = 0, + + VTABLE_LAST_ENTRY +} VtableType; + +typedef struct VTableMgr VTableMgr; + +VTableMgr* make_vtablemgr( MPFORMAL_NOCOMMA ); +void vtmgr_destroy( MPFORMAL VTableMgr* vtmgr ); + +void vtmgr_setVTable( VTableMgr* vtmgr, VtableType typ, void* vtable ); +void* vtmgr_getVTable( VTableMgr* vtmgr, VtableType typ ); + +#ifdef CPLUS +} +#endif + +#endif + + diff --git a/xwords4/common/xwproto.h b/xwords4/common/xwproto.h new file mode 100644 index 000000000..2d3f5cd6e --- /dev/null +++ b/xwords4/common/xwproto.h @@ -0,0 +1,53 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _XWPROTO_H_ +#define _XWPROTO_H_ + + + + +typedef enum { + XWPROTO_ERROR = 0, /* illegal value */ + XWPROTO_CHAT, /* reserved... */ + XWPROTO_DEVICE_REGISTRATION, /* client's first message to server */ + XWPROTO_CLIENT_SETUP, /* server's first message to client */ + XWPROTO_MOVEMADE_INFO_CLIENT, /* client reports a move it made */ + XWPROTO_MOVEMADE_INFO_SERVER, /* server tells all clients about a move + made by it or another client */ + XWPROTO_UNDO_INFO_CLIENT, /* client reports undo[s] on the device */ + XWPROTO_UNDO_INFO_SERVER, /* server reports undos[s] happening + elsewhere*/ + //XWPROTO_CLIENT_MOVE_INFO, /* client says "I made this move" */ + //XWPROTO_SERVER_MOVE_INFO, /* server says "Player X made this move" */ +/* XWPROTO_CLIENT_TRADE_INFO, */ +/* XWPROTO_TRADEMADE_INFO, */ + XWPROTO_BADWORD_INFO, + XWPROTO_MOVE_CONFIRM, /* server tells move sender that move was + legal */ + //XWPROTO_MOVEMADE_INFO, /* info about tiles placed and received */ + XWPROTO_CLIENT_REQ_END_GAME, /* non-server wants to end the game */ + XWPROTO_END_GAME /* server says to end game */ + + +} XW_Proto; + +#define XWPROTO_NBITS 4 + +#endif diff --git a/xwords4/common/xwstate.h b/xwords4/common/xwstate.h new file mode 100644 index 000000000..876f6b9a1 --- /dev/null +++ b/xwords4/common/xwstate.h @@ -0,0 +1,29 @@ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ +#ifndef XWSTATE_H_ +#define XWSTATE_H_ + +typedef enum { + XW_UNDEFINED, + + XW_SERVER_WAITING_CLIENT_SIGNON, + XW_SERVER_READY_TO_PLAY, +} XWGameState; + + +#endif diff --git a/xwords4/common/xwstream.h b/xwords4/common/xwstream.h new file mode 100644 index 000000000..8563047c4 --- /dev/null +++ b/xwords4/common/xwstream.h @@ -0,0 +1,136 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _XWSTREAM_H_ +#define _XWSTREAM_H_ + +#include "comtypes.h" +/* #include "xptypes.h" */ + +#define START_OF_STREAM 0 +#define END_OF_STREAM -1 + +typedef XP_U32 XWStreamPos; /* low 3 bits are bit offset; rest byte offset */ +enum { POS_READ, POS_WRITE }; +typedef XP_U8 PosWhich; + +typedef struct StreamCtxVTable { + void (*m_stream_destroy)( XWStreamCtxt* dctx ); + + XP_U8 (*m_stream_getU8)( XWStreamCtxt* dctx ); + void (*m_stream_getBytes)( XWStreamCtxt* dctx, void* where, + XP_U16 count ); + XP_U16 (*m_stream_getU16)( XWStreamCtxt* dctx ); + XP_U32 (*m_stream_getU32)( XWStreamCtxt* dctx ); + XP_U32 (*m_stream_getBits)( XWStreamCtxt* dctx, XP_U16 nBits ); + + void (*m_stream_putU8)( XWStreamCtxt* dctx, XP_U8 byt ); + void (*m_stream_putBytes)( XWStreamCtxt* dctx, void* whence, + XP_U16 count ); + void (*m_stream_putU16)( XWStreamCtxt* dctx, XP_U16 data ); + void (*m_stream_putU32)( XWStreamCtxt* dctx, XP_U32 data ); + void (*m_stream_putBits)( XWStreamCtxt* dctx, XP_U16 nBits, XP_U32 bits ); + + void (*m_stream_copyFromStream)( XWStreamCtxt* dctx, XWStreamCtxt* src, + XP_U16 nBytes ); + + XWStreamPos (*m_stream_getPos)( XWStreamCtxt* dctx, PosWhich which ); + XWStreamPos (*m_stream_setPos)( XWStreamCtxt* dctx, XWStreamPos newpos, + PosWhich which ); + + void (*m_stream_open)( XWStreamCtxt* dctx ); + void (*m_stream_close)( XWStreamCtxt* dctx ); + + XP_U16 (*m_stream_getSize)( XWStreamCtxt* dctx ); + +/* void (*m_stream_makeReturnAddr)( XWStreamCtxt* dctx, XP_PlayerAddr* addr, */ +/* XP_U16* addrLen ); */ + + XP_PlayerAddr (*m_stream_getAddress)( XWStreamCtxt* dctx ); + void (*m_stream_setAddress)( XWStreamCtxt* dctx, XP_PlayerAddr channelNo ); + +} StreamCtxVTable; + + +struct XWStreamCtxt { + StreamCtxVTable* vtable; +}; + + +#define stream_destroy(sc) \ + (sc)->vtable->m_stream_destroy(sc) + +#define stream_getU8(sc) \ + (sc)->vtable->m_stream_getU8(sc) + +#define stream_getBytes(sc, wh, c ) \ + (sc)->vtable->m_stream_getBytes((sc), (wh), (c)) + +#define stream_getU16(sc) \ + (sc)->vtable->m_stream_getU16(sc) + +#define stream_getU32(sc) \ + (sc)->vtable->m_stream_getU32(sc) + +#define stream_getBits(sc, n) \ + (sc)->vtable->m_stream_getBits((sc), (n)) + +#define stream_putU8(sc, b) \ + (sc)->vtable->m_stream_putU8((sc), (b)) + +#define stream_putBytes( sc, w, c ) \ + (sc)->vtable->m_stream_putBytes((sc), (w), (c)) + +#define stream_putU16(sc, d) \ + (sc)->vtable->m_stream_putU16((sc), (d)) + +#define stream_putU32(sc, d) \ + (sc)->vtable->m_stream_putU32((sc), (d)) + +#define stream_putBits(sc, n, b) \ + (sc)->vtable->m_stream_putBits((sc), (n), (b)) + +#define stream_copyFromStream( sc, src, nb ) \ + (sc)->vtable->m_stream_copyFromStream((sc), (src), (nb)) + +#define stream_getPos(sc, w) \ + (sc)->vtable->m_stream_getPos((sc), (w)) + +#define stream_setPos(sc, p, w) \ + (sc)->vtable->m_stream_setPos((sc), (p), (w)) + +#define stream_open(sc) \ + (sc)->vtable->m_stream_open((sc)) + +#define stream_close(sc) \ + (sc)->vtable->m_stream_close((sc)) + +#define stream_getSize(sc) \ + (sc)->vtable->m_stream_getSize((sc)) + +#define stream_makeReturnAddr(sc,addr,len) \ + (sc)->vtable->m_stream_makeReturnAddr((sc),(addr),(len)) + +#define stream_getAddress(sc) \ + (sc)->vtable->m_stream_getAddress((sc)) + +#define stream_setAddress(sc,ch) \ + (sc)->vtable->m_stream_setAddress((sc),(ch)) + +#endif /* _XWSTREAM_H_ */ diff --git a/xwords4/linux/LocalizedStrIncludes.h b/xwords4/linux/LocalizedStrIncludes.h new file mode 100644 index 000000000..909501e27 --- /dev/null +++ b/xwords4/linux/LocalizedStrIncludes.h @@ -0,0 +1,51 @@ +/* Copyright 2001 by Eric House (fixin@peak.org) (fixin@peak.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. + */ + + +/* This is the linux version of what's always been a palm file. There's + * probably a better way of doing this, but this is it for now. + */ + +#ifndef _LOCALIZEDSTRINCLUDES_H_ +#define _LOCALIZEDSTRINCLUDES_H_ + +enum { + STRD_REMAINING_TILES_ADD, + STRD_UNUSED_TILES_SUB, + STR_COMMIT_CONFIRM, + STRD_TURN_SCORE, + STR_BONUS_ALL, + STR_NONLOCAL_NAME, + STRD_TIME_PENALTY_SUB, + + STRD_CUMULATIVE_SCORE, + STRS_TRAY_AT_START, + STRS_MOVE_DOWN, + STRS_MOVE_ACROSS, + STRS_NEW_TILES, + STRSS_TRADED_FOR, + STR_PASS, + STR_PHONY_REJECTED, + STRD_ROBOT_TRADED, + STR_ROBOT_MOVED, + STR_REMOTE_MOVED, + + STR_LAST +}; + + +#endif diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile new file mode 100644 index 000000000..93df67300 --- /dev/null +++ b/xwords4/linux/Makefile @@ -0,0 +1,120 @@ +# -*- mode: makefile -*- +# Copyright 2002 by Eric House (fixin@peak.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. + +PLATFORM=linux +TARGET=$(PLATFORM)/xwords +CC = gcc + +include ../common/config.mk + +DEFINES = -DPLATFORM_LINUX -DKEY_SUPPORT -DKEYBOARD_NAV -DNODE_CAN_4 +DEFINES += -DSTUBBED_DICT +# build with GTK +DEFINES += -DPLATFORM_GTK +# build with ncurses (-u flag chooses over gtk if both present) +DEFINES += -DPLATFORM_NCURSES +#DEFINES += -DDRAW_WITH_PRIMITIVES +DEFINES += -DBEYOND_IR + +#-DDEBUG -DEIGHT_TILES + +#GPROFFLAG = -pg + +# INCLUDES += -I/usr/lib/glib/include +INCLUDES += -I$(HOME)/usr/local/include/ -I/home/ehouse/usr/local/lib/glib/include -I$(HOME)/usr/local/pilot/include + +OBJ = $(PLATFORM)/linuxmain.o \ + $(PLATFORM)/linuxdict.o \ + $(PLATFORM)/gtkmain.o \ + $(PLATFORM)/gtkdraw.o \ + $(PLATFORM)/gtkask.o \ + $(PLATFORM)/gtkletterask.o \ + $(PLATFORM)/gtkpasswdask.o \ + $(PLATFORM)/gtknewgame.o \ + $(PLATFORM)/cursesmain.o \ + $(PLATFORM)/cursesdraw.o \ + $(PLATFORM)/cursesask.o \ + $(PLATFORM)/filestream.o \ + +# $(PLATFORM)/linuxcommpipe.o \ + +LIBS = -lm -L $(HOME)/usr/local/pilot/lib $(GPROFFLAG) +# -lprc +# -pg +CFLAGS = -g -Wall -O2 $(GPROFFLAG) + +ifneq (,$(findstring DPLATFORM_GTK,$(DEFINES))) +# LIBS += `pkg-config --libs gtk+-2.0` +# CFLAGS += `pkg-config --cflags gtk+-2.0` -DPOINTER_SUPPORT + LIBS += $$(gtk-config --libs) + CFLAGS += `gtk-config --cflags` -DPOINTER_SUPPORT +endif + +ifneq (,$(findstring DPLATFORM_NCURSES,$(DEFINES))) + LIBS += -lncurses +endif + +ifeq (,$(findstring -DCLIENT_ONLY,$(DEFINES))) +# LIBS += -lprc +endif + +# provides an all: target +include ../common/rules.mk + +help: + @echo -n "make debug | curses_debug | gtk_debug | " + @echo "curses_client_debug | gtk_client_debug" + +#test: +# $(MAKE) test1 DEFINES="$(DEFINES) -FOOBAR" + +#test1: +# echo $(findstring FOO,$(DEFINES)) +# echo $(DEFINES) + +curses_client_debug: + $(MAKE) $(TARGET) DEFINES="$(DEFINES) -DDEBUG -DPLATFORM_NCURSES -DCLIENT_ONLY" + +curses_debug: + $(MAKE) $(TARGET) DEFINES="$(DEFINES) -DDEBUG -DPLATFORM_NCURSES" + +gtk_client_debug: + $(MAKE) $(TARGET) DEFINES="$(DEFINES) -DDEBUG -DPLATFORM_GTK -DCLIENT_ONLY" + +gtk_debug: + $(MAKE) $(TARGET) DEFINES="$(DEFINES) -DDEBUG -DPLATFORM_GTK" + +debug: + $(MAKE) $(TARGET) DEFINES="$(DEFINES) -DDEBUG -DPLATFORM_NCURSES -DPLATFORM_GTK" + +memdebug: + $(MAKE) $(TARGET) DEFINES="$(DEFINES) -DDEBUG -DPLATFORM_NCURSES -DPLATFORM_GTK -DMEM_DEBUG" + +gprof: + $(MAKE) $(TARGET) GPROFFLAG=-pg DEFINES="$(DEFINES) -DPLATFORM_NCURSES -DPLATFORM_GTK" + +$(TARGET): $(COMMONOBJ) $(OBJ) *.h Makefile + @ls -d $(PLATFORM) >/dev/null || mkdir $(PLATFORM) + $(CC) $(CFLAGS) $(DEFINES) $(COMMONOBJ) $(OBJ) $(LIBS) -o $@ + +$(PLATFORM)/%.o: %.c + @ls -d $(PLATFORM) >/dev/null || mkdir $(PLATFORM) + $(CC) -c $(INCLUDES) $(DEFINES) -DPLATFORM=$(PLATFORM) $(CFLAGS) $< -o $@ + +clean: + rm -rf $(PLATFORM)/*.o $(TARGET) + cd ../common && $(MAKE) PLATFORM=$(PLATFORM) $@ diff --git a/xwords4/linux/README.txt b/xwords4/linux/README.txt new file mode 100644 index 000000000..9107b51a3 --- /dev/null +++ b/xwords4/linux/README.txt @@ -0,0 +1,52 @@ +This directory contains the desktop Linux port of Crosswords. + +To build, run a shell in this directory and type + +# make +or +# make debug +or +# make memdebug + +Any will work as long as you have both libncurses and libgtk-1.2 and +the associated headers installed on your system. If you don't you can +play with the Makefile to build with only GTK or ncurses. + +Once you've built, go to the linux directory that will be created +within this one and type, at a minimum + +# ./xwords -s -n SomeName + +to get a GTK-based game with the built-in (English) tiles. (Add the +-u flag to run with ncurses instead of GTK.) There will be no robot +player, and the hint feature ('?' button) won't work. For that you +need a real dictionary, which you can build in the dawg directory. If +you build the BasEnglish2to8.xwd one in dawg/English, this command +will run a two person game between you and the machine: + +# ./xwords -s -r robot -n SomeName -d ../../dawg/English/BasEnglish2to8.xwd + +Here are the commands to launch two copies playing against each other +over the network. Do these in separate shells both in the same +directory as the above commands ran in. Launch the one with the -s +flag (the "server") first. + + s1# ./xwords -s -r Eric -N -p 4000 -l 4001 + s2# ./xwords -d ../../dawg/English/BasEnglish2to8.xwd -r Kati -p 4001 -l 6002 + +Both of these have "robot" players. Turn one or both -r flags to -n +for human players who make their own moves. + +If you want to run them on different machines, just add the -a flag to +the client telling it on what machine to find the server (since it +sends the first message, and the server will use the return address +from that message.) + + + +***** + +Please keep in mind that these Linux desktop clients are meant for +development only, as testbeds for code in ../common/ that will also be +used for the "real" products on PalmOS, PocketPC, eBookman, etc. +They're not supposed to be polished. diff --git a/xwords4/linux/cursesask.c b/xwords4/linux/cursesask.c new file mode 100644 index 000000000..849821def --- /dev/null +++ b/xwords4/linux/cursesask.c @@ -0,0 +1,189 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +/* Put up a "dialog" with a question and tab-selectable buttons below. + * On , return the number of the button selected at the time. + */ + +#ifdef PLATFORM_NCURSES + +#include + +#include "cursesask.h" + +#define ASK_HEIGHT 5 +#define PAD 2 +#define MAX_LINES 15 +#define MIN_WIDTH 25 + +static void +drawButtons( WINDOW* confWin, XP_U16 line, short spacePerButton, + short numButtons, short curSelButton, char** button1 ) +{ + short i; + for ( i = 0; i < numButtons; ++i ) { + short len = strlen( *button1 ); + + if ( i == curSelButton ) { + wstandout( confWin ); + } + mvwprintw( confWin, line, ((i+1) * spacePerButton) - (len/2), + "[%s]", *button1 ); + if ( i == curSelButton ) { + wstandend( confWin ); + } + ++button1; + } + wrefresh( confWin ); +} /* drawButtons */ + +/* Figure out how many lines there are and how wide the widest is. + */ +typedef struct FormatInfo { + XP_U16 nLines; + XP_U16 maxLen; + struct { + XP_UCHAR* substr; + XP_U16 len; + } line[MAX_LINES]; +} FormatInfo; + +static void +measureAskText( XP_UCHAR* question, FormatInfo* fip ) +{ + XP_U16 i; + XP_U16 maxWidth = 0; + XP_Bool done = XP_FALSE; + + for ( i = 0; i < MAX_LINES && !done; ++i ) { + XP_UCHAR* next = strstr( question, XP_CR ); + XP_U16 thisWidth; + + fip->line[i].substr = question; + + if ( !!next ) { + thisWidth = next - question; + } else { + thisWidth = strlen(question); + done = XP_TRUE; + } + fip->line[i].len = thisWidth; + + if ( thisWidth > maxWidth ) { + maxWidth = thisWidth; + } + + question = next + strlen(XP_CR); + } + + fip->nLines = i; + fip->maxLen = maxWidth; +} /* measureAskText */ + +short +cursesask( CursesAppGlobals* globals, char* question, short numButtons, + char* button1, ... ) +{ + WINDOW* confWin; + int x, y, rows, row, nLines; + short newSelButton = 0; + short curSelButton = 1; /* force draw by being different */ + short spacePerButton, num; + short maxWidth; + XP_Bool dismissed = XP_FALSE; + FormatInfo fi; + int len; + + measureAskText( question, &fi ); + len = fi.maxLen; + if ( len < MIN_WIDTH ) { + len = MIN_WIDTH; + } + + getmaxyx(globals->boardWin, y, x); + + rows = fi.nLines; + maxWidth = x - (PAD*2) - 2; /* 2 for two borders */ + + if ( len > x-2 ) { + rows = (len / maxWidth) + 1; + len = maxWidth; + } + + nLines = ASK_HEIGHT + rows - 1; + confWin = newwin( nLines, len+(PAD*2), + (y/2) - (nLines/2), (x-len-2)/2 ); + + wclear( confWin ); + box( confWin, '|', '-'); + + for ( row = 0; row < rows; ++row ) { + mvwaddnstr( confWin, row+1, PAD, + fi.line[row].substr, fi.line[row].len ); + } + spacePerButton = (len+(PAD*2)) / (numButtons + 1); + + while ( !dismissed ) { + int ch; + + if ( newSelButton != curSelButton ) { + drawButtons( confWin, rows+1, spacePerButton, numButtons, + curSelButton=newSelButton, &button1 ); + } + + ch = fgetc( stdin ); +/* char ch = wgetch( globals->menuWin ); */ + switch ( ch ) { + case '\t': + case 'L': + newSelButton = (curSelButton+1) % numButtons; + break; + case 'H': + newSelButton = (numButtons+curSelButton-1) % numButtons; + break; + case EOF: + case 4: /* C-d */ + case 27: /* ESC */ + curSelButton = 0; /* should be the cancel case */ + case '\r': + case '\n': + dismissed = XP_TRUE; + break; + case '1': + case '2': + case '3': + case '4': + num = ch - '1'; + if ( num < numButtons ) { + newSelButton = num; + } + break; + default: + beep(); + } + } + delwin( confWin ); + + /* this leaves a ghost line, but I can't figure out a better way. */ + wtouchln( globals->boardWin, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); + wrefresh( globals->boardWin ); + return curSelButton; +} /* ask */ + +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesask.h b/xwords4/linux/cursesask.h new file mode 100644 index 000000000..9ad614802 --- /dev/null +++ b/xwords4/linux/cursesask.h @@ -0,0 +1,29 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _CURSESASK_H_ +#define _CURSESASK_H_ + +#include "cursesmain.h" + +short cursesask( CursesAppGlobals* globals, char* question, + short numButtons, char* button1, ... ); + + +#endif diff --git a/xwords4/linux/cursesdraw.c b/xwords4/linux/cursesdraw.c new file mode 100644 index 000000000..a0953823f --- /dev/null +++ b/xwords4/linux/cursesdraw.c @@ -0,0 +1,500 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2000 by Eric House (fixin@peak.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. + */ + +#ifdef PLATFORM_NCURSES + +#include +#include + + +#include "cursesmain.h" +#include "draw.h" +#include "board.h" + +static void +drawRect( WINDOW* win, XP_Rect* rect, char vert, char hor ) +{ + wmove( win, rect->top-1, rect->left ); + whline( win, hor, rect->width ); + wmove( win, rect->top+rect->height, rect->left ); + whline( win, hor, rect->width ); + + wmove( win, rect->top, rect->left-1 ); + wvline( win, vert, rect->height ); + wmove( win, rect->top, rect->left+rect->width ); + wvline( win, vert, rect->height ); +} /* drawRect */ + +static void +eraseRect( CursesDrawCtx* dctx, XP_Rect* rect ) +{ + int y, bottom = rect->top + rect->height; + for ( y = rect->top; y < bottom; ++y ) { + mvwhline( dctx->boardWin, y, rect->left, ' ', rect->width ); + } +} /* eraseRect */ + +static void +curses_draw_destroyCtxt( DrawCtx* p_dctx ) +{ + // CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; +} /* draw_setup */ + +static void +curses_draw_boardBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool hasfocus ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + if ( hasfocus ) { + drawRect( dctx->boardWin, rect, '+', '+' ); + } else { + drawRect( dctx->boardWin, rect, '|', '-' ); + } +} /* draw_finish */ + +static void +curses_draw_trayBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_U16 owner, + XP_Bool hasfocus ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + if ( hasfocus ) { + drawRect( dctx->boardWin, rect, '+', '+' ); + } else { + drawRect( dctx->boardWin, rect, '|', '-' ); + } +} /* draw_finish */ + +static void +curses_draw_scoreBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_U16 numPlayers, + XP_Bool hasfocus ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + if ( hasfocus ) { + drawRect( dctx->boardWin, rect, '+', '+' ); + } else { + drawRect( dctx->boardWin, rect, '|', '-' ); + } + +} /* curses_draw_scoreBegin */ + +static void +formatRemText( char* buf, XP_S16 nTilesLeft ) +{ + strcpy( buf, "Tiles left in pool: " ); + buf += strlen( buf ); + if ( nTilesLeft < 0 ) { + strcpy( buf, "***" ); + } else { + sprintf( buf, "%.3d", nTilesLeft ); + } +} /* formatRemText */ + +static void +curses_draw_measureRemText( DrawCtx* dctx, XP_Rect* r, + XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ) +{ + char buf[32]; + + formatRemText( buf, nTilesLeft ); + + *width = strlen(buf); + *height = 1; +} /* curses_draw_measureRemText */ + +static void +curses_draw_drawRemText( DrawCtx* p_dctx, XP_Rect* rInner, XP_Rect* rOuter, + XP_S16 nTilesLeft ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + char buf[32]; + + formatRemText( buf, nTilesLeft ); + mvwprintw( dctx->boardWin, rInner->top, rInner->left, buf ); +} /* curses_draw_drawRemText */ + +static void +formatScoreText( char* buf, DrawScoreInfo* dsi ) +{ + XP_S16 nTilesLeft = dsi->nTilesLeft; + char label; + XP_Bool isRobot = dsi->isRobot; + + if ( nTilesLeft < 0 ) { + nTilesLeft = MAX_TRAY_TILES; + } + + if ( dsi->isRemote ) { + if ( isRobot ) { + label = 'R'; + } else { + label = 'N'; + } + } else { + if ( isRobot ) { + label = 'r'; + } else { + label = 'n'; + } + } + + sprintf( buf, "%s[%c] %s (%d)", (dsi->isTurn?"->":" "), + label, dsi->name, nTilesLeft ); +} /* formatScoreText */ + +static void +curses_draw_measureScoreText( DrawCtx* p_dctx, XP_Rect* r, + DrawScoreInfo* dsi, + XP_U16* width, XP_U16* height ) +{ + char buf[100]; + formatScoreText( buf, dsi ); + + *width = strlen( buf ); + *height = 1; /* one line per player */ +} /* curses_draw_measureScoreText */ + +static void +curses_draw_score_pendingScore( DrawCtx* p_dctx, XP_Rect* rect, XP_S16 score, + XP_U16 playerNum ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + char buf[4]; + + if ( score >= 0 ) { + sprintf( buf, "%.3d", score ); + } else { + strcpy( buf, "???" ); + } + + mvwprintw( dctx->boardWin, rect->top+1, rect->left, "pt:" ); + mvwprintw( dctx->boardWin, rect->top+2, rect->left, "%s", buf ); + wrefresh( dctx->boardWin ); +} /* curses_draw_score_pendingScore */ + +static void +curses_draw_boardFinished( DrawCtx* p_dctx ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wrefresh( dctx->boardWin ); +} /* curses_draw_boardFinished */ + +static void +curses_draw_trayFinished( DrawCtx* p_dctx ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wrefresh( dctx->boardWin ); +} /* draw_finished */ + +static void +curses_draw_scoreFinished( DrawCtx* p_dctx ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wrefresh( dctx->boardWin ); +} /* draw_finished */ + +#define MY_PAIR 1 + +static void +curses_draw_score_drawPlayer( DrawCtx* p_dctx, XP_S16 playerNum, + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + char curSBuf[6]; + char buf[100]; + int y = rInner->top; + + if ( dsi->selected ) { + wstandout( dctx->boardWin ); + } + /* first blank out the whole thing! */ + mvwhline( dctx->boardWin, y, rOuter->left, ' ', rOuter->width ); + + /* print the name and turn/remoteness indicator */ + formatScoreText( buf, dsi ); + mvwprintw( dctx->boardWin, y, rOuter->left, buf ); + + if ( 0 && dsi->isTurn/* && !remote */ ) { + /* print score:curscore at the right edge. If curscore is illegal, + replace with "??" */ + mvwprintw( dctx->boardWin, y, rOuter->left + rOuter->width - 7, + "%s:%.3d", curSBuf, dsi->score ); + } else { + mvwprintw( dctx->boardWin, y, rOuter->left + rOuter->width - 3, + "%.3d", dsi->score ); + } + + if ( dsi->selected ) { + wstandend( dctx->boardWin ); + } + /* (void)wcolor_set( dctx->boardWin, prev, NULL ); */ +} /* curses_draw_score_drawPlayer */ + +static XP_Bool +curses_draw_drawCell( DrawCtx* p_dctx, XP_Rect* rect, + XP_UCHAR* letter, XP_Bitmap bitmap, + XP_S16 owner, XWBonusType bonus, XP_Bool isBlank, + XP_Bool highlight, XP_Bool isStar ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + + if ( *letter == LETTER_NONE ) { + switch ( bonus ) { + case BONUS_DOUBLE_LETTER: + letter[0] = '+'; break; + case BONUS_DOUBLE_WORD: + letter[0] = '*'; break; + case BONUS_TRIPLE_LETTER: + letter[0] = '^'; break; + case BONUS_TRIPLE_WORD: + letter[0] = '#'; break; + default: + letter[0] = ' '; + } /* switch */ + } + + if ( highlight ) { + wstandout( dctx->boardWin ); + } + + mvwaddnstr( dctx->boardWin, rect->top, rect->left, letter, + strlen(letter) ); + + if ( highlight ) { + wstandend( dctx->boardWin ); + } + + return XP_TRUE; +} /* curses_draw_drawCell */ + +static void +curses_stringInTile( CursesDrawCtx* dctx, XP_Rect* rect, + XP_UCHAR* letter, XP_UCHAR* val ) +{ + eraseRect( dctx, rect ); + + mvwaddnstr( dctx->boardWin, rect->top+1, rect->left+(rect->width/2), + letter, strlen(letter) ); + + if ( !!val ) { + int len = strlen( val ); + mvwaddnstr( dctx->boardWin, rect->top+rect->height-2, + rect->left + rect->width - len, val, len ); + } +} /* curses_stringInTile */ + +static void +curses_draw_drawTile( DrawCtx* p_dctx, XP_Rect* rect, + XP_UCHAR* textP, XP_Bitmap bitmap, + XP_S16 val, XP_Bool highlighted ) +{ + char numbuf[5]; + char letterbuf[5]; + char* nump = NULL; + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + + letterbuf[0] = !!textP? *textP: '_'; /* BLANK or bitmap */ + letterbuf[1] = '\0'; + if ( val >= 0 ) { + sprintf( numbuf, "%.2d", val ); + if ( numbuf[0] == '0' ) { + numbuf[0] = ' '; + } + nump = numbuf; + } + + curses_stringInTile( dctx, rect, letterbuf, nump ); + + if ( highlighted ) { + mvwaddnstr( dctx->boardWin, rect->top+rect->height-1, + rect->left, "*-*", 3 ); + } +} /* curses_draw_drawTile */ + +static void +curses_draw_drawTileBack( DrawCtx* p_dctx, XP_Rect* rect ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + curses_stringInTile( dctx, rect, "?", "?" ); +} /* curses_draw_drawTileBack */ + +static void +curses_draw_drawTrayDivider( DrawCtx* p_dctx, XP_Rect* rect, + XP_Bool selected ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wmove( dctx->boardWin, rect->top, rect->left ); + wvline( dctx->boardWin, '#', rect->height ); + +} /* curses_draw_drawTrayDivider */ + +static void +curses_draw_drawBoardArrow( DrawCtx* p_dctx, XP_Rect* rect, + XWBonusType cursorBonus, XP_Bool vertical ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; +#if 1 + char ch = vertical?'|':'-'; + mvwaddch( dctx->boardWin, rect->top, rect->left, ch ); +#else + chtype curChar = mvwinch(dctx->boardWin, rect->top, rect->left ); + wstandout( dctx->boardWin ); + mvwaddch( dctx->boardWin, rect->top, rect->left, curChar); + wstandend( dctx->boardWin ); +#endif +} /* curses_draw_drawBoardArrow */ + +static void +curses_draw_drawBoardCursor( DrawCtx* p_dctx, XP_Rect* rect ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + chtype curChar = mvwinch(dctx->boardWin, rect->top, rect->left ); + wstandout( dctx->boardWin ); + mvwaddch( dctx->boardWin, rect->top, rect->left, curChar); + wstandend( dctx->boardWin ); +} /* curses_draw_drawBoardCursor */ + +static void +curses_draw_drawTrayCursor( DrawCtx* p_dctx, XP_Rect* rect ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wmove( dctx->boardWin, rect->top, rect->left ); + whline( dctx->boardWin, 'v', rect->width ); +} /* curses_draw_drawTrayCursor */ + +static void +curses_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rectP ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + XP_Rect rect = *rectP; + + eraseRect( dctx, &rect ); +} /* curses_draw_clearRect */ + +static XP_UCHAR* +curses_draw_getMiniWText( DrawCtx* p_dctx, XWMiniTextType textHint ) +{ + return "Trading..."; +} /* curses_draw_getMiniWText */ + +static void +curses_draw_measureMiniWText( DrawCtx* p_dctx, unsigned char* str, + XP_U16* widthP, XP_U16* heightP ) +{ + *widthP = strlen(str) + 4; + *heightP = 3; +} /* curses_draw_measureMiniWText */ + +static void +curses_draw_drawMiniWindow( DrawCtx* p_dctx, XP_UCHAR* text, + XP_Rect* rect, void** closure ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + XP_Rect smallerR; + + smallerR.top = rect->top + 1; + smallerR.left = rect->left + 1; + smallerR.width = rect->width - 2; + smallerR.height = rect->height - 2; + + eraseRect( dctx, rect ); + drawRect( dctx->boardWin, &smallerR, '|', '-' ); + + mvwprintw( dctx->boardWin, smallerR.top, smallerR.left, text, + strlen(text) ); +} /* curses_draw_drawMiniWindow */ + +static void +curses_draw_eraseMiniWindow( DrawCtx* p_dctx, XP_Rect* rect, + XP_Bool lastTime, void** closure, + XP_Bool* invalUnder ) +{ + *invalUnder = XP_TRUE; +} /* curses_draw_eraseMiniWindow*/ + +#if 0 +static void +curses_draw_frameTray( DrawCtx* p_dctx, XP_Rect* rect ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + box( dctx->boardWin, '*', '+'); +} /* curses_draw_frameTray */ +#endif + +static void +draw_doNothing( DrawCtx* dctx, ... ) +{ +} /* draw_doNothing */ + +DrawCtx* +cursesDrawCtxtMake( WINDOW* boardWin ) +{ + CursesDrawCtx* dctx = malloc( sizeof(CursesDrawCtx) ); + short i; + + dctx->vtable = malloc( sizeof(*(((CursesDrawCtx*)dctx)->vtable)) ); + + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + + SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_boardFinished, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayFinished, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreFinished, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardCursor, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayCursor, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_eraseMiniWindow, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, curses ); + + + /* SET_VTABLE_ENTRY( dctx, draw_getBonusText, gtk ); */ + /* SET_VTABLE_ENTRY( dctx, draw_eraseMiniWindow, gtk ); */ + + + /* SET_VTABLE_ENTRY( dctx, draw_frameTray, curses ); */ + + dctx->boardWin = boardWin; + + return (DrawCtx*)dctx; +} /* curses_drawctxt_init */ + +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c new file mode 100644 index 000000000..f5b4ebc77 --- /dev/null +++ b/xwords4/linux/cursesmain.c @@ -0,0 +1,950 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ +#ifdef PLATFORM_NCURSES + +#include +#include +#include +#include + +#include /* gethostbyname */ +#include +//#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "linuxmain.h" +#include "cursesmain.h" +#include "cursesask.h" +#include "model.h" +#include "draw.h" +#include "board.h" +#include "engine.h" +/* #include "compipe.h" */ +#include "xwproto.h" +#include "xwstream.h" +#include "xwstate.h" +#include "server.h" +#include "memstream.h" +#include "util.h" + +#define MENU_WINDOW_HEIGHT 5 /* three lines plus borders */ +#define INFINITE_TIMEOUT -1 + +CursesAppGlobals globals; /* must be global b/c of SIGWINCH_handler */ + +static void changeFocus( CursesAppGlobals* globals ); + +#ifdef MEM_DEBUG +# define MEMPOOL params->util->mpool, +#else +# define MEMPOOL +#endif + +/* extern int errno; */ + +static void +cursesUserError( CursesAppGlobals* globals, char* format, ... ) +{ + char buf[512]; + va_list ap; + + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + (void)cursesask( globals, buf, 1, "OK" ); + + va_end(ap); +} /* cursesUserError */ + +static void +curses_util_listenPortChange( XW_UtilCtxt* uc, XP_U16 newPort ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + XP_LOGF( "listenPortChange called: not sure what to do" ); + + /* if this isn't true, need to tear down and rebind socket */ + XP_ASSERT( newPort == globals->cGlobals.params->defaultListenPort ); +} /* curses_util_listenPortChange */ + +static void +curses_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + XP_UCHAR* message = linux_getErrString( id ); + + cursesUserError( globals, message ); +} /* curses_util_userError */ + +static XP_U16 +curses_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + CursesAppGlobals* globals; + char* question; + char* answers[3]; + short numAnswers = 0; + XP_Bool freeMe = XP_FALSE; + XP_U16 result; + + switch( id ) { + case QUERY_COMMIT_TURN: + question = strFromStream( stream ); + freeMe = XP_TRUE; + answers[numAnswers++] = "Cancel"; + answers[numAnswers++] = "Ok"; + break; + case QUERY_COMMIT_TRADE: + question = "Commit trade?"; + answers[numAnswers++] = "Cancel"; + answers[numAnswers++] = "Ok"; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + question = strFromStream( stream ); + freeMe = XP_TRUE; + answers[numAnswers++] = "Ok"; + break; + + default: + XP_ASSERT( 0 ); + return 0; + } + globals = (CursesAppGlobals*)uc->closure; + result = cursesask( globals, question, numAnswers, + answers[0], answers[1], answers[2] ); + + if ( freeMe ) { + free( question ); + } + + return result; +} /* curses_util_userQuery */ + +static void +curses_util_askBlankFace(XW_UtilCtxt* uc, DictionaryCtxt* dict, + XP_UCHAR* buf ) +{ + buf[0] = 'A'; + buf[1] = '\0'; +} /* curses_util_askBlankFace */ + +static void +curses_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState state ) +{ + /* nothing to do if we don't have a scrollbar */ +} /* curses_util_trayHiddenChange */ + +static void +cursesShowFinalScores( CursesAppGlobals* globals ) +{ + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + server_writeFinalScores( globals->cGlobals.game.server, stream ); + + text = strFromStream( stream ); + + (void)cursesask( globals, text, 1, "Ok" ); + + free( text ); +} /* cursesShowFinalScores */ + +static void +curses_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + board_draw( globals->cGlobals.game.board ); + + /* game belongs in cGlobals... */ + if ( globals->cGlobals.params->printHistory ) { + catGameHistory( &globals->cGlobals ); + } + + if ( globals->cGlobals.params->quitAfter ) { + globals->timeToExit = XP_TRUE; + } else if ( globals->cGlobals.params->undoWhenDone ) { + server_handleUndo( globals->cGlobals.game.server ); + } else { + cursesShowFinalScores( globals ); + } +} /* curses_util_notifyGameOver */ + +static XP_Bool +curses_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + return XP_TRUE; +} /* curses_util_hiliteCell */ + +static XP_Bool +curses_util_engineProgressCallback( XW_UtilCtxt* uc ) +{ + return XP_TRUE; +} /* curses_util_engineProgressCallback */ + +static void +curses_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why ) +{ + XP_ASSERT( 0 ); /* no pen-down events..... */ +} /* curses_util_setTimer */ + +static void +curses_util_requestTime( XW_UtilCtxt* uc ) +{ + /* I've created a pipe whose read-only end is plugged into the array of + fds that my event loop polls so that I can write to it to simulate + post-event on a more familiar system. It works, so no complaints! */ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + (void)write( globals->timepipe[1], "!", 1 ); +} /* curses_util_requestTime */ + +static void +initCurses( CursesAppGlobals* globals ) +{ + WINDOW* mainWin; + WINDOW* menuWin; + WINDOW* boardWin; + + int x, y; + + /* ncurses man page says most apps want this sequence */ + mainWin = initscr(); + cbreak(); + noecho(); + nonl(); + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + + getmaxyx(mainWin, y, x); + globals->statusLine = y - MENU_WINDOW_HEIGHT - 1; + menuWin = newwin( MENU_WINDOW_HEIGHT, x, y-MENU_WINDOW_HEIGHT, 0 ); + nodelay(menuWin, 1); /* don't block on getch */ + boardWin = newwin( MAX_ROWS+1, x, 0, 0 ); + +/* leaveok( boardWin, 1 ); */ +/* leaveok( menuWin, 1 ); */ + + globals->menuWin = menuWin; + globals->boardWin = boardWin; + globals->mainWin = mainWin; +} /* initCurses */ + +#if 0 +static void +showStatus( CursesAppGlobals* globals ) +{ + char* str; + + switch ( globals->state ) { + case XW_SERVER_WAITING_CLIENT_SIGNON: + str = "Waiting for client[s] to connnect"; + break; + case XW_SERVER_READY_TO_PLAY: + str = "It's somebody's move"; + break; + default: + str = "unknown state"; + } + + + standout(); + mvaddstr( globals->statusLine, 0, str ); +/* clrtoeol(); */ + standend(); + + refresh(); +} /* showStatus */ +#endif + +typedef XP_Bool (*CursesMenuHandler)(CursesAppGlobals* globals); +typedef struct MenuList { + CursesMenuHandler handler; + char* desc; + char* keyDesc; + char key; +} MenuList; +static XP_Bool +handleQuit( CursesAppGlobals* globals ) +{ + globals->timeToExit = XP_TRUE; + return XP_TRUE; +} /* handleQuit */ + +static XP_Bool +handleTab( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_FOCUSCHANGE_KEY ); + changeFocus( globals ); + return XP_TRUE; +} /* handleTab */ + +static XP_Bool +handleRet( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_RETURN_KEY ); + return XP_TRUE; +} /* handleRet */ + +static XP_Bool +handleHint( CursesAppGlobals* globals ) +{ + XP_Bool redo; + globals->doDraw = board_requestHint( globals->cGlobals.game.board, &redo ); + return XP_TRUE; +} /* handleHint */ + +static XP_Bool +handleCommit( CursesAppGlobals* globals ) +{ + globals->doDraw = board_commitTurn( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleCommit */ + +static XP_Bool +handleJuggle( CursesAppGlobals* globals ) +{ + globals->doDraw = board_juggleTray( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleJuggle */ + +static XP_Bool +handleHide( CursesAppGlobals* globals ) +{ + XW_TrayVisState curState = + board_getTrayVisState( globals->cGlobals.game.board ); + + if ( curState == TRAY_REVEALED ) { + globals->doDraw = board_hideTray( globals->cGlobals.game.board ); + } else { + globals->doDraw = board_showTray( globals->cGlobals.game.board ); + } + + return XP_TRUE; +} /* handleJuggle */ + +static XP_Bool +handleFlip( CursesAppGlobals* globals ) +{ + globals->doDraw = board_flip( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleFlip */ + +static XP_Bool +handleToggleValues( CursesAppGlobals* globals ) +{ + globals->doDraw = board_toggle_showValues( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleToggleValues */ + +static XP_Bool +handleBackspace( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_CURSOR_KEY_DEL ); + return XP_TRUE; +} /* handleBackspace */ + +static XP_Bool +handleUndo( CursesAppGlobals* globals ) +{ + globals->doDraw = server_handleUndo( globals->cGlobals.game.server ); + return XP_TRUE; +} /* handleUndo */ + +static XP_Bool +handleReplace( CursesAppGlobals* globals ) +{ + globals->doDraw = board_replaceTiles( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleReplace */ + +MenuList sharedMenuList[] = { + { handleQuit, "Quit", "Q", 'Q' }, + { handleTab, "Change focus", "", '\t' }, + { handleRet, "Click/tap", "", '\r' }, + { handleHint, "Hint", "?", '?' }, + { handleCommit, "Commit move", "C", 'C' }, + { handleFlip, "Flip", "F", 'F' }, + { handleToggleValues, "Show values", "V", 'V' }, + + { handleBackspace, "Remove from board", "", 8 }, + { handleUndo, "Undo prev", "U", 'U' }, + { handleReplace, "uNdo cur", "N", 'N' }, + + { NULL, NULL, NULL, '\0'} +}; + +static XP_Bool +handleLeft( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_CURSOR_KEY_LEFT ); + return XP_TRUE; +} /* handleLeft */ + +static XP_Bool +handleDivLeft( CursesAppGlobals* globals ) +{ + globals->doDraw = board_moveDivider( globals->cGlobals.game.board, + XP_FALSE ); + return XP_TRUE; +} /* handleDivLeft */ + +static XP_Bool +handleDivRight( CursesAppGlobals* globals ) +{ + globals->doDraw = board_moveDivider( globals->cGlobals.game.board, + XP_TRUE ); + return XP_TRUE; +} /* handleDivRight */ + +static XP_Bool +handleRight( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_CURSOR_KEY_RIGHT ); + return XP_TRUE; +} /* handleRight */ + +static XP_Bool +handleUp( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_CURSOR_KEY_UP ); + return XP_TRUE; +} /* handleUp */ + +static XP_Bool +handleDown( CursesAppGlobals* globals ) +{ + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_CURSOR_KEY_DOWN ); + return XP_TRUE; +} /* handleDown */ + +MenuList boardMenuList[] = { + { handleLeft, "Left", "H", 'H' }, + { handleRight, "Right", "L", 'L' }, + { handleUp, "Up", "J", 'J' }, + { handleDown, "Down", "K", 'K' }, + + { NULL, NULL, NULL, '\0'} +}; + +MenuList scoreMenuList[] = { + { handleUp, "Up", "J", 'J' }, + { handleDown, "Down", "K", 'K' }, + + { NULL, NULL, NULL, '\0'} +}; + +MenuList trayMenuList[] = { + { handleLeft, "Left", "H", 'H' }, + { handleRight, "Right", "L", 'L' }, + { handleDivLeft, "Div left", "{", '{' }, + { handleDivRight, "Div right", "}", '}' }, + { handleJuggle, "Juggle", "J", 'J' }, + { handleHide, "[un]hIde", "I", 'I' }, + + { NULL, NULL, NULL, '\0'} +}; + +static void +figureMaxes( MenuList* mList, short maxLines, short* maxKeyP, short* maxCmdP ) +{ + short i; + + *maxKeyP = *maxCmdP = 0; + + for ( i = 0; i < maxLines && mList->handler != NULL; ++i ) { + short keyLen = strlen(mList->keyDesc); + short cmdLen = strlen(mList->desc); + *maxKeyP = XP_MAX( *maxKeyP, keyLen ); + *maxCmdP= XP_MAX( *maxCmdP, cmdLen ); + ++mList; + } +} /* figureMaxes */ + +static void +drawMenuFromList( CursesAppGlobals* globals, MenuList* menuList ) +{ + short i; + short maxKey = 0, maxCmd = 0; + short line = 0, col; + short nLines; + int winMaxY, winMaxX; + WINDOW* win = globals->menuWin; + XP_Bool done = XP_FALSE; + + getmaxyx( win, winMaxY, winMaxX ); + + nLines = globals->nLinesMenu; + if ( nLines == 0 ) { + nLines = 1; + } + + for ( ; !done; ++nLines ) { + MenuList* entry = sharedMenuList; + XP_Bool isShared = XP_TRUE; + + wclear( win ); + + maxKey = maxCmd = 0; + for ( line = 0, col = -2, i = 0; ; ++entry, ++line ) { + char* key; + + if ( entry->handler == NULL ) { + if ( !isShared ) { + done = XP_TRUE; + break; + } else { + isShared = XP_FALSE; + entry = menuList; + XP_ASSERT( !!entry->handler ); + } + } + + if ( line % nLines == 0 ) { + line = 0; + col += maxKey + maxCmd + 2; + figureMaxes( entry, nLines, &maxKey, &maxCmd ); + if ( (col + maxCmd + strlen(entry->keyDesc)) >= winMaxX ) { + break; + } + } + + key = entry->keyDesc; + + wstandout( win ); + mvwaddstr( win, line, col+maxKey-strlen(key), key ); + wstandend( win ); + mvwaddstr( win, line, col+maxKey+1, entry->desc ); + } + } + + globals->nLinesMenu = nLines - 1; + + wrefresh( win ); +} /* drawMenuFromList */ + +static void +SIGWINCH_handler( int signal ) +{ + int x, y; + + assert( signal == SIGWINCH ); + + endwin(); + +/* (*globals.drawMenu)( &globals ); */ + + getmaxyx( stdscr, y, x ); + wresize( globals.mainWin, y-MENU_WINDOW_HEIGHT, x ); + + board_draw( globals.cGlobals.game.board ); +} /* SIGWINCH_handler */ + +static void +cursesListenOnSocket( CursesAppGlobals* globals, int newSock, + XWStreamCtxt* stream ) +{ + XP_ASSERT( globals->fdCount+1 < FD_MAX ); + + XP_WARNF( "setting fd[%d] to %d", globals->fdCount, newSock ); + globals->fdArray[globals->fdCount].fd = newSock; + globals->fdArray[globals->fdCount].events = POLLIN; + + globals->streams[globals->fdCount] = stream; + + ++globals->fdCount; + XP_LOGF( "listenOnSocket: there are now %d sources to poll", + globals->fdCount ); +} /* cursesListenOnSocket */ + + +/* + * Ok, so this doesn't block yet.... + */ +static XP_Bool +blocking_gotEvent( CursesAppGlobals* globals, int* ch ) +{ + XP_Bool result = XP_FALSE; + int numEvents; + short fdIndex; + XP_Bool redraw = XP_FALSE; + + numEvents = poll( globals->fdArray, globals->fdCount, INFINITE_TIMEOUT ); + + if ( numEvents > 0 ) { + + /* stdin first */ + if ( (globals->fdArray[FD_STDIN].revents & POLLIN) != 0 ) { + int evtCh = fgetc(stdin); + *ch = evtCh; + result = XP_TRUE; + --numEvents; + } + if ( (globals->fdArray[FD_STDIN].revents & ~POLLIN ) ) { + XP_LOGF( "some other events set on stdin" ); + } + + if ( (globals->fdArray[FD_TIMEEVT].revents & POLLIN) != 0 ) { + char ch; + /* XP_DEBUGF( "curses got a USER EVENT\n" ); */ + (void)read(globals->fdArray[FD_TIMEEVT].fd, &ch, 1 ); + } + + fdIndex = FD_FIRSTSOCKET; + + if ( numEvents > 0 && + (globals->fdArray[fdIndex].revents & POLLIN) != 0 ) { + int flen, nBytes; + XP_U8 buf[256]; + struct sockaddr_in addr_sock; + + --numEvents; + + flen = sizeof(addr_sock); + nBytes = recvfrom( globals->fdArray[fdIndex].fd, + buf, sizeof(buf), 0, + (struct sockaddr *)&addr_sock, &flen ); + + if ( nBytes == -1 ) { + XP_WARNF( "recvfrom: errno=%d\n", errno ); + } else { + XWStreamCtxt* inboundS; + XP_Bool redraw = XP_FALSE; + + XP_STATUSF( "recvfrom=>%d", nBytes ); + inboundS = stream_from_msgbuf( &globals->cGlobals, + buf, nBytes ); + if ( !!inboundS ) { + CommsAddrRec addrRec; + + XP_MEMSET( &addrRec, 0, sizeof(addrRec) ); + addrRec.conType = COMMS_CONN_IP; + + addrRec.u.ip.ipAddr = ntohl(addr_sock.sin_addr.s_addr); + XP_LOGF( "captured incomming ip address: 0x%lx", + addrRec.u.ip.ipAddr ); + + if ( comms_checkIncommingStream(globals->cGlobals.game.comms, + inboundS, + &addrRec ) ) { + XP_LOGF( "comms read port: %d", + addrRec.u.ip.port ); + redraw = server_receiveMessage( globals->cGlobals.game.server, + inboundS ); + } + stream_destroy( inboundS ); + } + + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the + screen before giving the server another shot. So just call + the idle proc. */ + if ( redraw ) { + curses_util_requestTime(globals->cGlobals.params->util); + } + } + + ++fdIndex; + } + + redraw = server_do( globals->cGlobals.game.server ) || redraw; + if ( redraw ) { + /* messages change a lot */ + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + } + } + return result; +} /* blocking_gotEvent */ + +static void +changeFocus( CursesAppGlobals* globals ) +{ + BoardObjectType focussed = + board_getFocusOwner( globals->cGlobals.game.board ); + if ( focussed == OBJ_TRAY ) { + globals->menuList = trayMenuList; + drawMenuFromList( globals, trayMenuList ); + } else if ( focussed == OBJ_BOARD ) { + globals->menuList = boardMenuList; + drawMenuFromList( globals, boardMenuList ); + } else if ( focussed == OBJ_SCORE ) { + globals->menuList = scoreMenuList; + drawMenuFromList( globals, scoreMenuList ); + } else { + XP_ASSERT(0); + } +} /* changeFocus */ + +#if 0 +static void +initClientSocket( CursesAppGlobals* globals, char* serverName ) +{ + struct hostent* hostinfo; + hostinfo = gethostbyname( serverName ); + if ( !hostinfo ) { + userError( globals, "unable to get host info for %s\n", serverName ); + } else { + char* hostName = inet_ntoa( *(struct in_addr*)hostinfo->h_addr ); + XP_LOGF( "gethostbyname returned %s", hostName ); + globals->csInfo.client.serverAddr = inet_addr(hostName); + XP_LOGF( "inet_addr returned %lu", + globals->csInfo.client.serverAddr ); + } +} /* initClientSocket */ +#endif + +static VTableMgr* +curses_util_getVTManager(XW_UtilCtxt* uc) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + return globals->cGlobals.params->vtMgr; +} /* linux_util_getVTManager */ + +static XP_Bool +curses_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + XP_WARNF( "curses_util_askPassword not implemented" ); + return XP_FALSE; +} /* curses_util_askPassword */ + +static void +curses_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset, XP_U16 newOffset ) +{ + if ( oldOffset != newOffset ) { + XP_WARNF( "curses_util_yOffsetChange(%d,%d) not implemented", + oldOffset, newOffset ); + } +} /* curses_util_yOffsetChange */ + +static XP_Bool +curses_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, + XP_Bool turnLost ) +{ + XP_WARNF( "curses_util_warnIllegalWord not implemented" ); + return XP_FALSE; +} /* curses_util_warnIllegalWord */ + +static void +cursesSendOnClose( XWStreamCtxt* stream, void* closure ) +{ + XP_S16 result; + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + + XP_LOGF( "cursesSendOnClose called" ); + result = comms_send( globals->cGlobals.game.comms, COMMS_CONN_IP, stream ); +} /* cursesSendOnClose */ + +static XWStreamCtxt* +curses_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + LaunchParams* params = globals->cGlobals.params; + + XWStreamCtxt* stream = mem_stream_make( MPPARM(uc->mpool) + params->vtMgr, + uc->closure, channelNo, + cursesSendOnClose ); + return stream; +} /* curses_util_makeStreamFromAddr */ + +static void +setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) +{ + util->vtable->m_util_userError = curses_util_userError; + + util->vtable->m_util_getVTManager = curses_util_getVTManager; + util->vtable->m_util_askPassword = curses_util_askPassword; + util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange; + util->vtable->m_util_warnIllegalWord = curses_util_warnIllegalWord; + util->vtable->m_util_makeStreamFromAddr = curses_util_makeStreamFromAddr; + + util->vtable->m_util_userQuery = curses_util_userQuery; + util->vtable->m_util_askBlankFace = curses_util_askBlankFace; + util->vtable->m_util_trayHiddenChange = curses_util_trayHiddenChange; + util->vtable->m_util_notifyGameOver = curses_util_notifyGameOver; + util->vtable->m_util_hiliteCell = curses_util_hiliteCell; + util->vtable->m_util_engineProgressCallback = + curses_util_engineProgressCallback; + util->vtable->m_util_setTimer = curses_util_setTimer; + util->vtable->m_util_requestTime = curses_util_requestTime; + + util->vtable->m_util_listenPortChange = curses_util_listenPortChange; + + util->vtable->m_util_requestTime = curses_util_requestTime; + + util->closure = globals; +} /* setupCursesUtilCallbacks */ + +static void +sendOnClose( XWStreamCtxt* stream, void* closure ) +{ + CursesAppGlobals* globals = closure; + XP_LOGF( "curses sendOnClose called" ); + XP_ASSERT( !!globals->cGlobals.game.comms ); + comms_send( globals->cGlobals.game.comms, COMMS_CONN_IP, stream ); +} /* sendOnClose */ + +static XP_Bool +handleKeyEvent( CursesAppGlobals* globals, MenuList* list, char ch ) +{ + while ( list->handler != NULL ) { + if ( list->key == ch ) { + if ( (*list->handler)(globals) ) { + return XP_TRUE; + } + } + ++list; + } + return XP_FALSE; +} /* handleKeyEvent */ + +static XP_Bool +passKeyToBoard( CursesAppGlobals* globals, char ch ) +{ + XP_Bool handled = ch >= 'a' && ch <= 'z'; + if ( handled ) { + ch += 'A' - 'a'; + globals->doDraw = board_handleKey( globals->cGlobals.game.board, ch ); + } + XP_LOGF( "passKeyToBoard: handled=%d on %x (%c)", handled, ch, ch ); + return handled; +} /* passKeyToBoard */ + +void +cursesmain( XP_Bool isServer, LaunchParams* params ) +{ + int piperesult; + int sock; + DictionaryCtxt* dict; + CommsAddrRec addr; + + memset( &globals, 0, sizeof(globals) ); + + globals.amServer = isServer; + globals.cGlobals.params = params; + + globals.cp.showBoardArrow = XP_TRUE; + globals.cp.showRobotScores = params->showRobotScores; + + dict = params->dict; + + setupCursesUtilCallbacks( &globals, params->util ); + + globals.cGlobals.defaultServerName = params->info.clientInfo.serverName; + + cursesListenOnSocket( &globals, 0, NULL ); /* stdin */ + + piperesult = pipe( globals.timepipe ); + XP_ASSERT( piperesult == 0 ); + + cursesListenOnSocket( &globals, globals.timepipe[0], NULL ); /* reader pipe */ + + sock = initListenerSocket( params->defaultListenPort ); + cursesListenOnSocket( &globals, sock, NULL ); + + signal( SIGWINCH, SIGWINCH_handler ); + initCurses( &globals ); + + globals.draw = (CursesDrawCtx*)cursesDrawCtxtMake( globals.boardWin ); + + game_makeNewGame( MEMPOOL &globals.cGlobals.game, ¶ms->gi, + params->util, (DrawCtx*)globals.draw, + &globals.cp, linux_udp_send, &globals ); + + addr.conType = COMMS_CONN_IP; + addr.u.ip.ipAddr = 0; /* ??? */ + addr.u.ip.port = params->defaultSendPort; + comms_setAddr( globals.cGlobals.game.comms, + &addr, params->defaultListenPort ); + + model_setDictionary( globals.cGlobals.game.model, params->dict ); + + board_setPos( globals.cGlobals.game.board, 1, 1, XP_FALSE ); + board_setScale( globals.cGlobals.game.board, 1, 1 ); + board_setScoreboardLoc( globals.cGlobals.game.board, 20, 1, 50, + 5, /*4 players + rem*/ XP_FALSE ); + + board_setTrayLoc( globals.cGlobals.game.board, 25, 8, 3, 4, 1 ); + /* no divider -- yet */ + /* board_setTrayVisible( globals.board, XP_TRUE, XP_FALSE ); */ + + board_invalAll( globals.cGlobals.game.board ); + + /* send any events that need to get off before the event loop begins */ + if ( !isServer ) { + if ( 1 /* stream_open( params->info.clientInfo.stream ) */) { + server_initClientConnection( globals.cGlobals.game.server, + mem_stream_make( MEMPOOL + params->vtMgr, + &globals, + (XP_PlayerAddr)0, + sendOnClose ) ); +#if 0 + cursesListenOnSocket( + &globals, NULL, /* replaces below */ + /* linux_getStreamSocket( params->info.clientInfo.stream ), */ + params->info.clientInfo.stream ); +#endif + } else { + cursesUserError( &globals, "Unable to open connection to server"); + exit( 0 ); + } + } + + server_do( globals.cGlobals.game.server ); + + globals.menuList = boardMenuList; + drawMenuFromList( &globals, boardMenuList ); + board_draw( globals.cGlobals.game.board ); + + while ( !globals.timeToExit ) { + int ch; + if ( blocking_gotEvent( &globals, &ch ) + && (handleKeyEvent( &globals, globals.menuList, ch ) + || handleKeyEvent( &globals, sharedMenuList, ch ) + || passKeyToBoard( &globals, ch ) ) ) { + if ( globals.doDraw ) { + board_draw( globals.cGlobals.game.board ); + globals.doDraw = XP_FALSE; + } + } + } + + endwin(); +} /* cursesmain */ +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h new file mode 100644 index 000000000..9e640b5df --- /dev/null +++ b/xwords4/linux/cursesmain.h @@ -0,0 +1,119 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2000 by Eric House (fixin@peak.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. + */ + +#ifndef _CURSESMAIN_H_ +#define _CURSESMAIN_H_ + + +#include +#include +#include +#include +#include + +#include + +#include "draw.h" +#include "main.h" +#include "board.h" +#include "model.h" +#include "dictnry.h" +#include "xwstream.h" +#include "comms.h" +#include "server.h" +#include "xwstate.h" +#include "util.h" +/* #include "compipe.h" */ + +typedef struct CursesDrawCtx { + DrawCtxVTable* vtable; + + WINDOW* boardWin; +/* WINDOW* trayWin; */ + +} CursesDrawCtx; + +typedef struct CursesAppGlobals CursesAppGlobals; + +typedef XP_Bool (*EventFunc)(CursesAppGlobals* globals, int ch); +/* typedef void (*MenuDrawer)(CursesAppGlobals* globals); */ + +#define FD_MAX 6 +#define FD_STDIN 0 +#define FD_TIMEEVT 1 +#define FD_FIRSTSOCKET 2 + +struct CursesAppGlobals { + CommonGlobals cGlobals; + + CursesDrawCtx* draw; + + DictionaryCtxt* dictionary; + EngineCtxt* engine; + CommonPrefs cp; + + XP_Bool amServer; /* this process acting as server */ + + WINDOW* mainWin; + WINDOW* menuWin; + WINDOW* boardWin; + + XP_Bool timeToExit; + XP_Bool doDraw; + struct MenuList* menuList; + XP_U16 nLinesMenu; + + union { + struct { + XWStreamCtxt* stream; /* how we can reach the server */ + } client; + struct { + int serverSocket; + XP_Bool socketOpen; + } server; + } csInfo; + + short statusLine; + XWGameState state; + + struct sockaddr_in listenerSockAddr; + short fdCount; + struct pollfd fdArray[FD_MAX]; /* one for stdio, one for listening socket */ + XWStreamCtxt* streams[FD_MAX]; /* [0], for stdin, will be null.... */ + + int timepipe[2]; /* for reading/writing "user events" */ +}; + + +DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); + +/* Ports: Client and server pick a port at startup on which they'll listen. + * If both are to be on the same device using localhost as their ip address, + * then they need to be listening on different ports. Server finds out what + * port client is listening on from the return address of the first message + * client sends -- I think. (I'm not sure that when I create a socket to use + * to SEND to the server that I specify the port on which I'm listening. + * Maybe I need to include that in a platform-specific part of the connect + * message.... Clearly there will need to be such a thing. + */ + + +void cursesmain( XP_Bool isServer, LaunchParams* params ); + +#endif diff --git a/xwords4/linux/filestream.c b/xwords4/linux/filestream.c new file mode 100644 index 000000000..295cb0d7a --- /dev/null +++ b/xwords4/linux/filestream.c @@ -0,0 +1,169 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ +#if 0 +#include + +#include "xwstream.h" + +typedef struct LinuxFileStreamCtxt { + StreamCtxVTable* vtable; + + FILE* file; +} LinuxFileStreamCtxt; + +static void make_vtable( LinuxFileStreamCtxt* stream ); + + +XWStreamCtxt* +linux_make_fileStream( char* fileName, XP_Bool forWriting ) +{ + LinuxFileStreamCtxt* result = malloc( sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + make_vtable( result ); + + result->file = fopen( fileName, forWriting? "w":"r" ); + XP_ASSERT( result->file ); + + return (XWStreamCtxt*)result; +} /* linux_make_fileStream */ + +static void +linux_file_stream_getBytes( XWStreamCtxt* p_sctx, void* where, + XP_U16 count ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + XP_ASSERT( !!stream->file ); + + fread( where, count, 1, stream->file ); +} /* linux_file_stream_getBytes */ + +static XP_U8 +linux_file_stream_getU8( XWStreamCtxt* p_sctx ) +{ + XP_U8 result; + linux_file_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* linux_file_stream_getU8 */ + +static XP_U16 +linux_file_stream_getU16( XWStreamCtxt* p_sctx ) +{ + XP_U16 result; + linux_file_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* linux_file_stream_getU16 */ + +static XP_U32 +linux_file_stream_getU32( XWStreamCtxt* p_sctx ) +{ + XP_U32 result; + linux_file_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* linux_file_stream_getU32 */ + +static void +linux_file_stream_putBytes( XWStreamCtxt* p_sctx, void* where, + XP_U16 count ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + size_t written; + XP_ASSERT( !!stream->file ); + + written = fwrite( where, count, 1, stream->file ); + XP_ASSERT( written != 0 ); +} /* linux_file_stream_putBytes */ + +static void +linux_file_stream_putU8( XWStreamCtxt* p_sctx, XP_U8 data ) +{ + linux_file_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_file_stream_putU8 */ + +static void +linux_file_stream_putU16( XWStreamCtxt* p_sctx, XP_U16 data ) +{ + linux_file_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_common_stream_putUI16 */ + +static void +linux_file_stream_putU32( XWStreamCtxt* p_sctx, XP_U32 data ) +{ + linux_file_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_file_stream_putUI32 */ + +static void +linux_file_stream_open( XWStreamCtxt* p_sctx ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + XP_ASSERT( !!stream->file ); + rewind( stream->file ); +} /* linux_file_stream_open */ + +static void +linux_file_stream_close( XWStreamCtxt* p_sctx ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + XP_ASSERT( !!stream->file ); + fclose( stream->file ); + stream->file = NULL; +} /* linux_file_stream_close */ + +static void +linux_file_stream_destroy( XWStreamCtxt* p_sctx ) +{ + LinuxFileStreamCtxt* stream; + + stream = (LinuxFileStreamCtxt*)p_sctx; + if ( !!stream->file ) { + stream_close( stream ); + } + + free( p_sctx->vtable ); + free( stream ); +} /* linux_file_stream_destroy */ + +static void +make_vtable( LinuxFileStreamCtxt* stream ) +{ + XP_ASSERT( !stream->vtable ); + stream->vtable = malloc( sizeof(*stream->vtable) ); + + SET_VTABLE_ENTRY( stream, stream_getU8, linux_file ); + SET_VTABLE_ENTRY( stream, stream_getBytes, linux_file ); + SET_VTABLE_ENTRY( stream, stream_getU16, linux_file ); + SET_VTABLE_ENTRY( stream, stream_getU32, linux_file ); + + SET_VTABLE_ENTRY( stream, stream_putU8, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putBytes, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putU16, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putU32, linux_file ); + + SET_VTABLE_ENTRY( stream, stream_destroy, linux_file ); + SET_VTABLE_ENTRY( stream, stream_open, linux_file ); + SET_VTABLE_ENTRY( stream, stream_close, linux_file ); + + /* PENDING(ehouse) These are part of some subclass, not of stream + overall */ +/* SET_VTABLE_ENTRY( stream, stream_getSize, linux_file ); */ +/* SET_VTABLE_ENTRY( stream, stream_getAddress, linux_file ); */ +/* SET_VTABLE_ENTRY( stream, stream_setAddress, linux_file ); */ +} /* make_vtable */ +#endif + diff --git a/xwords4/linux/filestream.h b/xwords4/linux/filestream.h new file mode 100644 index 000000000..c2479707b --- /dev/null +++ b/xwords4/linux/filestream.h @@ -0,0 +1,26 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _FILESTREAM_H_ +#define _FILESTREAM_H_ + +XWStreamCtxt* linux_make_fileStream( char* fileName, XP_Bool forWriting ); + + +#endif diff --git a/xwords4/linux/flip.xpm b/xwords4/linux/flip.xpm new file mode 100644 index 000000000..fabecbd13 --- /dev/null +++ b/xwords4/linux/flip.xpm @@ -0,0 +1,14 @@ +/* XPM */ +static char * flip_xpm[] = { +"8 8 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"........", +".+......", +".++.....", +".+++....", +".++++...", +".+++++..", +".++++++.", +"........"}; diff --git a/xwords4/linux/gtkask.c b/xwords4/linux/gtkask.c new file mode 100644 index 000000000..b2d7cf3d6 --- /dev/null +++ b/xwords4/linux/gtkask.c @@ -0,0 +1,83 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ +#ifdef PLATFORM_GTK + +#include + +#include "gtkask.h" + +static void +button_event( GtkWidget* widget, void* closure ) +{ + gboolean* whichSet = (gboolean*)closure; + *whichSet = 1; + + gtk_main_quit(); +} + +gint +gtkask( GtkAppGlobals* globals, gchar *message, gint numButtons, + char* button1, ... ) +{ + GtkWidget* dialog; + GtkWidget* label; + GtkWidget* button; + short i; + gboolean* results = g_malloc( numButtons * sizeof(results[0]) ); + char** butList = &button1; + + /* Create the widgets */ + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + label = gtk_label_new( message ); + + for ( i = 0; i < numButtons; ++i ) { + button = gtk_button_new_with_label( *butList ); + + results[i] = 0; + gtk_signal_connect( GTK_OBJECT( button ), "clicked", + GTK_SIGNAL_FUNC(button_event), &results[i] ); + + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), + button ); + + ++butList; + } + + /* Add the label, and show everything we've added to the dialog. */ + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label); + gtk_widget_show_all (dialog); + + /* returns when button handler calls gtk_main_quit */ + gtk_main(); + + gtk_widget_destroy( dialog ); + + for ( i = 0; i < numButtons; ++i ) { + if ( results[i] ) { + break; + } + } + g_free( results ); + return i; + } /* gtkask */ + +#endif diff --git a/xwords4/linux/gtkask.h b/xwords4/linux/gtkask.h new file mode 100644 index 000000000..6517b49a9 --- /dev/null +++ b/xwords4/linux/gtkask.h @@ -0,0 +1,32 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + + +#ifdef PLATFORM_GTK + +#ifndef _GTKASK_H_ +#define _GTKASK_H_ + +#include "gtkmain.h" + +gint gtkask( GtkAppGlobals* globals, gchar *message, gint numButtons, + char* button1, ... ); + +#endif +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c new file mode 100644 index 000000000..a023982c3 --- /dev/null +++ b/xwords4/linux/gtkdraw.c @@ -0,0 +1,913 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2002 by Eric House (fixin@peak.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. + */ +#ifdef PLATFORM_GTK + +#include +#include + + +#include "gtkmain.h" +#include "draw.h" +#include "board.h" +#include "linuxmain.h" + +/* static GdkGC* newGCForColor( GdkWindow* window, XP_Color* newC ); */ +static void +insetRect( XP_Rect* r, short i ) +{ + r->top += i; + r->left += i; + i *= 2; + + r->width -= i; + r->height -= i; +} /* insetRect */ + +//#define DRAW_WHAT(dc) ((dc)->pixmap) +#define DRAW_WHAT(dc) ((dc)->widget->window) + +static void +eraseRect(GtkDrawCtx* dctx, XP_Rect* rect ) +{ + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->widget->style->white_gc, + TRUE, rect->left, rect->top, + rect->width, rect->height ); +} /* eraseRect */ + +static void +frameRect( GtkDrawCtx* dctx, XP_Rect* rect ) +{ + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, FALSE, rect->left, rect->top, + rect->width, rect->height ); +} /* frameRect */ + +#ifdef DRAW_WITH_PRIMITIVES + +static void +gtk_prim_draw_setClip( DrawCtx* p_dctx, XP_Rect* newClip, XP_Rect* oldClip) +{ +} /* gtk_prim_draw_setClip */ + +static void +gtk_prim_draw_frameRect( DrawCtx* p_dctx, XP_Rect* rect ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + frameRect( dctx, rect ); +} /* gtk_prim_draw_frameRect */ + +static void +gtk_prim_draw_invertRect( DrawCtx* p_dctx, XP_Rect* rect ) +{ + /* not sure you can do this on GTK!! */ +} /* gtk_prim_draw_invertRect */ + +static void +gtk_prim_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rect ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + eraseRect( dctx, rect ); +} /* gtk_prim_draw_clearRect */ + +static void +gtk_prim_draw_drawString( DrawCtx* p_dctx, XP_UCHAR* str, + XP_U16 x, XP_U16 y ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_U16 fontHeight = 10; /* FIX ME */ + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + x, y + fontHeight, str ); +} /* gtk_prim_draw_drawString */ + +static void +gtk_prim_draw_drawBitmap( DrawCtx* p_dctx, XP_Bitmap bm, + XP_U16 x, XP_U16 y ) +{ +} /* gtk_prim_draw_drawBitmap */ + +static void +gtk_prim_draw_measureText( DrawCtx* p_dctx, XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + gint len = strlen(str); + gint width = gdk_text_measure( dctx->gdkFont, str, len ); + + *widthP = width; + *heightP = 12; /* ??? :-) */ +} /* gtk_prim_draw_measureText */ + +#endif /* DRAW_WITH_PRIMITIVES */ + +static void +drawBitmapFromLBS( GtkDrawCtx* dctx, XP_Bitmap bm, XP_Rect* rect ) +{ + GdkPixmap* pm; + LinuxBMStruct* lbs = (LinuxBMStruct*)bm; + gint x, y; + XP_U8* bp; + XP_U16 i; + XP_S16 nBytes; + XP_U16 nCols, nRows; + + nCols = lbs->nCols; + nRows = lbs->nRows; + bp = (XP_U8*)(lbs + 1); /* point to the bitmap data */ + nBytes = lbs->nBytes; + + pm = gdk_pixmap_new( DRAW_WHAT(dctx), nCols, nRows, -1 ); + + gdk_draw_rectangle( pm, dctx->widget->style->white_gc, TRUE, + 0, 0, nCols, nRows ); + + x = 0; + y = 0; + + while ( nBytes-- ) { + XP_U8 byte = *bp++; + for ( i = 0; i < 8; ++i ) { + XP_Bool draw = ((byte & 0x80) != 0); + if ( draw ) { + gdk_draw_point( pm, dctx->widget->style->black_gc, x, y ); + } + byte <<= 1; + if ( ++x == nCols ) { + x = 0; + if ( ++y == nRows ) { + break; + } + } + } + } + + XP_ASSERT( nBytes == -1 ); /* else we're out of sync */ + + gdk_draw_pixmap( DRAW_WHAT(dctx), + dctx->drawGC, + (GdkDrawable*)pm, 0, 0, + rect->left+2, + rect->top+2, + lbs->nCols, + lbs->nRows ); + + gdk_pixmap_unref( pm ); +} /* drawBitmapFromLBS */ + +#if 0 +static void +gtk_draw_destroyCtxt( DrawCtx* p_dctx ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + GtkAllocation* alloc = &dctx->widget->allocation; + + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->widget->style->white_gc, + TRUE, + 0, 0, alloc->width, alloc->height ); + +} /* draw_setup */ +#endif + +static void +gtk_draw_boardBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool hasfocus ) +{ + GdkRectangle gdkrect; + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + + gdkrect = *(GdkRectangle*)rect; + ++gdkrect.width; + ++gdkrect.height; + gdk_gc_set_clip_rectangle( dctx->drawGC, &gdkrect ); + +/* gdk_draw_rectangle( DRAW_WHAT(dctx), */ +/* dctx->drawGC, FALSE, */ +/* rect->left, rect->top, rect->width, rect->height ); */ + +} /* draw_finish */ + +static void +gtk_draw_boardFinished( DrawCtx* p_dctx ) +{ + // GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; +} /* draw_finished */ + +static XP_Bool +gtk_draw_drawCell( DrawCtx* p_dctx, XP_Rect* rect, XP_UCHAR* letter, + XP_Bitmap bitmap, XP_S16 owner, XWBonusType bonus, + XP_Bool isBlank, XP_Bool highlight, XP_Bool isStar ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect rectInset = *rect; + XP_Bool showGrid = dctx->globals->gridOn; + + eraseRect( dctx, rect ); + + insetRect( &rectInset, 1 ); + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + + if ( showGrid ) { + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, + rect->left, rect->top, rect->width, + rect->height ); + } + + /* draw the bonus colors only if we're not putting a "tile" there */ + if ( !!letter ) { + if ( *letter == LETTER_NONE && bonus != BONUS_NONE ) { + XP_ASSERT( bonus <= 4 ); + gdk_gc_set_foreground( dctx->drawGC, &dctx->bonusColors[bonus-1] ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + TRUE, + rectInset.left, rectInset.top, + rectInset.width+1, rectInset.height+1 ); + + } else { + gint len = strlen(letter); + gint width = gdk_text_measure( dctx->gdkFont, letter, len ); + gint x = rect->left; + x += (rect->width - width) / 2; + + if ( highlight ) { + gdk_gc_set_foreground( dctx->drawGC, &dctx->red ); + } else { + gdk_gc_set_foreground( dctx->drawGC, + owner >= 0? &dctx->playerColors[owner]: + &dctx->black ); + } + gdk_draw_text( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + x, rect->top+rect->height-1, letter, len ); + + if ( isBlank ) { + gdk_draw_arc( DRAW_WHAT(dctx), dctx->drawGC, + 0, /* filled */ + rect->left, /* x */ + rect->top, /* y */ + rect->width,/*width, */ + rect->height,/*width, */ + 0, 360*64 ); + } + } + } else if ( !!bitmap ) { + drawBitmapFromLBS( dctx, bitmap, rect ); + } + + if ( isStar ) { + letter = "*"; + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_text( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + rect->left+3, rect->top+rect->height-1, + letter, 1 ); + } + + return XP_TRUE; +} /* gtk_draw_drawCell */ + +static void +gtk_draw_invertCell( DrawCtx* p_dctx, XP_Rect* rect ) +{ +/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */ +/* (void)gtk_draw_drawMiniWindow( p_dctx, "f", rect); */ + +/* GdkGCValues values; */ + +/* gdk_gc_get_values( dctx->drawGC, &values ); */ + +/* gdk_gc_set_function( dctx->drawGC, GDK_INVERT ); */ + +/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */ +/* gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, */ +/* TRUE, rect->left, rect->top, */ +/* rect->width, rect->height ); */ + +/* gdk_gc_set_function( dctx->drawGC, values.function ); */ +} /* gtk_draw_invertCell */ + +static void +gtk_draw_trayBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_U16 owner, + XP_Bool hasfocus ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect clip = *rect; + insetRect( &clip, -1 ); + gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)&clip ); +/* eraseRect( dctx, rect ); */ +} /* gtk_draw_trayBegin */ + +static void +gtk_draw_drawTile( DrawCtx* p_dctx, XP_Rect* rect, XP_UCHAR* textP, + XP_Bitmap bitmap, XP_S16 val, XP_Bool highlighted ) +{ + unsigned char numbuf[3]; + gint len; + gint width; + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect insetR = *rect; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + + /* insetRect( &insetR, 1 ); */ /* to draw inside the thing */ + /* ++insetR.left; */ + /* --insetR.width; */ + eraseRect( dctx, &insetR ); + + if ( val < 0 ) { + } else { + insetRect( &insetR, 1 ); + + if ( !!textP ) { + if ( *textP != LETTER_NONE ) { /* blank */ + gdk_draw_text( DRAW_WHAT(dctx), dctx->gdkTrayFont, dctx->drawGC, + insetR.left + 2, + insetR.top + dctx->trayFontHeight, + textP, 1 ); + } + } else if ( !!bitmap ) { + drawBitmapFromLBS( dctx, bitmap, &insetR ); + } + + sprintf( numbuf, "%d", val ); + len = strlen( numbuf ); + width = gdk_text_measure( dctx->gdkFont, numbuf, len ); + + gdk_draw_text( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + insetR.left+insetR.width - width - 1, + insetR.top + insetR.height - 2, + numbuf, len ); + + /* frame the tile */ + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, + insetR.left, insetR.top, insetR.width, + insetR.height ); + + if ( highlighted ) { + insetRect( &insetR, 1 ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, insetR.left, insetR.top, + insetR.width, insetR.height); + } + } +} /* gtk_draw_drawTile */ + +static void +gtk_draw_drawTileBack( DrawCtx* p_dctx, XP_Rect* rect ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect r = *rect; + + insetRect( &r, 1 ); + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, TRUE, + r.left, r.top, r.width, r.height ); +} /* gtk_draw_drawTileBack */ + +static void +gtk_draw_drawTrayDivider( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool selected ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect r = *rect; + + eraseRect( dctx, &r ); + + ++r.left; + r.width -= selected? 2:1; + if ( selected ) { + --r.height; + } + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + !selected, + r.left, r.top, r.width, r.height); + +} /* gtk_draw_drawTrayDivider */ + +#if 0 +static void +gtk_draw_frameBoard( DrawCtx* p_dctx, XP_Rect* rect ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, FALSE, + rect->left, rect->top, rect->width, rect->height ); + +} /* gtk_draw_frameBoard */ + +static void +gtk_draw_frameTray( DrawCtx* p_dctx, XP_Rect* rect ) +{ + // GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; +} /* gtk_draw_frameBoard */ +#endif + +static void +gtk_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rectP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect rect = *rectP; + + ++rect.width; + ++rect.top; + + eraseRect( dctx, &rect ); + +} /* gtk_draw_clearRect */ + +static void +gtk_draw_drawBoardArrow( DrawCtx* p_dctx, XP_Rect* rectP, + XWBonusType cursorBonus, XP_Bool vertical ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; +/* XP_Rect rect = *rectP; */ + char curs = vertical? '|':'-'; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_text( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + rectP->left+3, rectP->top+rectP->height-1, + &curs, 1 ); + +} /* gtk_draw_drawBoardCursor */ + +static void +gtk_draw_scoreBegin( DrawCtx* p_dctx, XP_Rect* rect, XP_U16 numPlayers, + XP_Bool hasfocus ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + + gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); + eraseRect( dctx, rect ); +} /* gtk_draw_scoreBegin */ + +static void +gtkDrawDrawRemText( DrawCtx* p_dctx, XP_Rect* r, XP_U16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + GtkAppGlobals* globals = dctx->globals; + XP_Bool isVertical = globals->cGlobals.params->verticalScore; + char buf[10]; + char* bufp = buf; + XP_U16 height, width; + XP_U16 left = r->left; + XP_U16 top = r->top; + XP_Bool draw = !widthP; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + sprintf( buf, "rem:%d", nTilesLeft ); + width = 2 + gdk_text_measure( dctx->gdkFont, buf, strlen(buf) ); + + if ( isVertical ) { + height = 12; + left += 2; + if ( width > r->width ) { + if ( draw ) { + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + left + 2, top + height, "rem:" ); + } + bufp += 4; + top += 12; + } + } else { + height = r->height; + } + + if ( draw ) { + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + left, top + height, bufp ); + } else { + *widthP = width; + *heightP = height; + } +} /* gtkDrawDrawRemText */ + +static void +gtk_draw_measureRemText( DrawCtx* p_dctx, XP_Rect* r, XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ) +{ + gtkDrawDrawRemText( p_dctx, r, nTilesLeft, width, height ); +} /* gtk_draw_measureRemText */ + +static void +gtk_draw_drawRemText( DrawCtx* p_dctx, XP_Rect* rInner, XP_Rect* rOuter, + XP_S16 nTilesLeft ) +{ + gtkDrawDrawRemText( p_dctx, rInner, nTilesLeft, NULL, NULL ); +} /* gtk_draw_drawRemText */ + +static void +widthAndText( char* buf, GdkFont* font, DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_S16 score = dsi->score; + XP_U16 nTilesLeft = dsi->nTilesLeft; + XP_Bool isTurn = dsi->isTurn; + char* borders = ""; + if ( isTurn ) { + borders = "*"; + } + + sprintf( buf, "%s%.3d", borders, score ); + if ( nTilesLeft < MAX_TRAY_TILES ) { + char nbuf[10]; + sprintf( nbuf, ":%d", nTilesLeft ); + (void)strcat( buf, nbuf ); + } + (void)strcat( buf, borders ); + + if ( !!widthP ) { + *widthP = gdk_string_measure( font, buf ); + *heightP = HOR_SCORE_HEIGHT; /* a wild-ass guess */ + } +} /* widthAndText */ + +static void +gtk_draw_measureScoreText( DrawCtx* p_dctx, XP_Rect* r, + DrawScoreInfo* dsi, + XP_U16* width, XP_U16* height ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char buf[20]; + GdkFont* font = /* dsi->selected? dctx->gdkBoldFont : */dctx->gdkFont; + + widthAndText( buf, font, dsi, width, height ); +} /* gtk_draw_measureScoreText */ + +static void +gtk_draw_score_drawPlayer( DrawCtx* p_dctx, XP_S16 playerNum, + XP_Rect* rInner, XP_Rect* rOuter, + DrawScoreInfo* dsi ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char scoreBuf[20]; + XP_U16 x; + GdkFont* font = /* dsi->selected? dctx->gdkBoldFont : */dctx->gdkFont; + + + widthAndText( scoreBuf, font, dsi, NULL, NULL ); + x = rInner->left;// + ((rect->width - width) /2); + + gdk_gc_set_foreground( dctx->drawGC, + playerNum >= 0? &dctx->playerColors[playerNum]: + &dctx->black + /*selected? &dctx->red:&dctx->black*/ ); + + if ( dsi->selected ) { + gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, + TRUE, rOuter->left, rOuter->top, + rOuter->width, rOuter->height ); + eraseRect( dctx, rInner ); + } + + gdk_draw_string( DRAW_WHAT(dctx), font, dctx->drawGC, + x, rInner->top + rInner->height, scoreBuf ); +} /* gtk_draw_score_drawPlayer */ + +static void +gtk_draw_score_pendingScore( DrawCtx* p_dctx, XP_Rect* rect, XP_S16 score, + XP_U16 playerNum ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char buf[5]; + XP_U16 left; + XP_Rect localR; + + if ( score >= 0 ) { + sprintf( buf, "%.3d", score ); + } else { + strcpy( buf, "???" ); + } + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); + + localR = *rect; + rect = &localR; + insetRect( rect, 1 ); + eraseRect( dctx, rect ); + + left = rect->left + 1; + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + left, rect->top + (rect->height/2), "Pts:" ); + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + left, rect->top + rect->height, buf ); +} /* gtk_draw_score_pendingScore */ + +static void +gtk_draw_scoreFinished( DrawCtx* p_dctx ) +{ +/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */ + +} /* gtk_draw_scoreFinished */ + +static void +gtkFormatTimerText( XP_UCHAR* buf, XP_S16 secondsLeft ) +{ + XP_U16 minutes, seconds; + + if ( secondsLeft < 0 ) { + *buf++ = '-'; + secondsLeft *= -1; + } + + minutes = secondsLeft / 60; + seconds = secondsLeft % 60; + sprintf( buf, "% 1d:%02d", minutes, seconds ); +} /* gtkFormatTimerText */ + +static void +gtk_draw_drawTimer( DrawCtx* p_dctx, XP_Rect* rInner, XP_Rect* rOuter, + XP_U16 player, XP_S16 secondsLeft ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_UCHAR buf[10]; + + gtkFormatTimerText( buf, secondsLeft ); + + gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rInner ); + eraseRect( dctx, rInner ); + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + rInner->left, rInner->top + rInner->height, buf ); +} /* gtk_draw_drawTimer */ + +#define MINI_LINE_HT 12 +#define MINI_V_PADDING 6 +#define MINI_H_PADDING 8 + +static unsigned char* +gtk_draw_getMiniWText( DrawCtx* p_dctx, XWMiniTextType textHint ) +{ +/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */ + unsigned char* str; + + switch( textHint ) { + case BONUS_DOUBLE_LETTER: + str = "Double letter"; break; + case BONUS_DOUBLE_WORD: + str = "Double word"; break; + case BONUS_TRIPLE_LETTER: + str = "Triple letter"; break; + case BONUS_TRIPLE_WORD: + str = "Triple word"; break; + case INTRADE_MW_TEXT: + str = "Trading tiles;\nclick D when done"; break; + default: + XP_ASSERT( XP_FALSE ); + } + return str; +} /* gtk_draw_getMiniWText */ + +static void +gtk_draw_measureMiniWText( DrawCtx* p_dctx, unsigned char* str, + XP_U16* widthP, XP_U16* heightP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_U16 height, maxWidth; + + for ( height = MINI_V_PADDING, maxWidth = 0; ; ) { + unsigned char* nextStr = strstr( str, "\n" ); + XP_U16 len = nextStr==NULL? strlen(str): nextStr - str; + + XP_U16 width = gdk_text_measure( dctx->gdkFont, str, len ); + maxWidth = XP_MAX( maxWidth, width ); + height += MINI_LINE_HT; + + if ( nextStr == NULL ) { + break; + } + str = nextStr+1; /* skip '\n' */ + } + + *widthP = maxWidth + MINI_H_PADDING; + *heightP = height; +} /* gtk_draw_measureMiniWText */ + +static void +gtk_draw_drawMiniWindow( DrawCtx* p_dctx, unsigned char* text, XP_Rect* rect, + void** closureP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect localR = *rect; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)&localR ); + + /* play some skanky games to get the shadow drawn under and to the + right... */ + eraseRect( dctx, &localR ); + + insetRect( &localR, 1 ); + --localR.width; + --localR.height; + frameRect( dctx, &localR ); + + --localR.top; + --localR.left; + eraseRect( dctx, &localR ); + frameRect( dctx, &localR ); + + for ( ; ; ) { /* draw up to the '\n' each time */ + unsigned char* nextStr = strstr( text, "\n" ); + XP_U16 len, width, left; + if ( nextStr == NULL ) { + len = strlen(text); + } else { + len = nextStr - text; + } + + localR.top += MINI_LINE_HT; + width = gdk_text_measure( dctx->gdkFont, text, len ); + left = localR.left + ((localR.width - width) / 2); + gdk_draw_text( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + left, localR.top, text, len ); + + if ( nextStr == NULL ) { + break; + } + text = nextStr+1; /* skip the CR */ + } +} /* gtk_draw_drawMiniWindow */ + +static void +gtk_draw_eraseMiniWindow( DrawCtx* p_dctx, XP_Rect* rect, XP_Bool lastTime, + void** closure, XP_Bool* invalUnder ) +{ +/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */ + *invalUnder = XP_TRUE; +} /* gtk_draw_eraseMiniWindow */ + +#define SET_GDK_COLOR( c, r, g, b ) { \ + c.red = (r); \ + c.green = (g); \ + c.blue = (b); \ +} +static void +draw_doNothing( DrawCtx* dctx, ... ) +{ +} /* draw_doNothing */ + +static void +allocAndSet( GdkColormap* map, GdkColor* color, unsigned short red, + unsigned short green, unsigned short blue ) + +{ + gboolean success; + + color->red = red; + color->green = green; + color->blue = blue; + + success = gdk_color_alloc( map, color ); + if ( !success ) { + XP_WARNF( "unable to alloc color" ); + } +} /* allocAndSet */ + +DrawCtx* +gtkDrawCtxtMake( GtkWidget *widget, GtkAppGlobals* globals ) +{ + GtkDrawCtx* dctx = g_malloc( sizeof(GtkDrawCtx) ); + GdkFont* font; + short i; + + dctx->vtable = g_malloc( sizeof(*(((GtkDrawCtx*)dctx)->vtable)) ); + + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, gtk ); + +#ifdef DRAW_WITH_PRIMITIVES + SET_VTABLE_ENTRY( dctx->vtable, draw_setClip, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_frameRect, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_invertRect, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawString, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBitmap, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureText, gtk_prim ); + + InitDrawDefaults( dctx->vtable ); +#else + + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_boardFinished, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreFinished, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_eraseMiniWindow, gtk ); + +#endif + +/* SET_VTABLE_ENTRY( dctx, draw_frameBoard, gtk_ ); */ +/* SET_VTABLE_ENTRY( dctx, draw_frameTray, gtk_ ); */ + + dctx->widget = widget; + dctx->globals = globals; + +/* font = gdk_font_load( "-*-new century schoolbook-medium-r-normal-" */ +/* "-14-100-100-100-p-82-iso8859-1" ); */ + font = gdk_font_load( "-adobe-new century schoolbook-medium-r-normal-" + "-10-100-75-75-p-60-iso8859-1" ); + + if ( font == NULL ) { + font = gdk_font_load( "-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*" ); + } + dctx->gdkFont = font; + + font = gdk_font_load( "-adobe-new century schoolbook-bold-r-normal-" + "-10-100-75-75-p-66-iso8859-1" ); + dctx->gdkBoldFont = font? font:dctx->gdkFont; + +/* font = gdk_font_load( "-*-new century schoolbook-bold-i-normal-" */ +/* "-24-240-75-75-p-148-iso8859-1" ); */ + font = gdk_font_load( "-adobe-new century schoolbook-bold-r-normal-" + "-12-120-75-75-p-77-iso8859-1" ); + + if ( font == NULL ) { + font = dctx->gdkFont; + } + dctx->gdkTrayFont = font; + + dctx->drawGC = gdk_gc_new( widget->window ); + dctx->trayFontHeight = 13; + + dctx->black.red = 0x0000; + dctx->black.pixel = 0x0000; + dctx->black.green = 0x0000; + dctx->black.blue = 0x0000; + + dctx->white.red = 0xFFFF; + dctx->white.pixel = 0x0000; + dctx->white.green = 0xFFFF; + dctx->white.blue = 0xFFFF; + + { + GdkColormap* map = gdk_colormap_get_system(); + + allocAndSet( map, &dctx->bonusColors[0], 0xFFFF, 0xAFFF, 0xAFFF ); + allocAndSet( map, &dctx->bonusColors[1], 0xAFFF, 0xFFFF, 0xAFFF ); + allocAndSet( map, &dctx->bonusColors[2], 0xAFFF, 0xAFFF, 0xFFFF ); + allocAndSet( map, &dctx->bonusColors[3], 0xFFFF, 0xAFFF, 0xFFFF ); + + allocAndSet( map, &dctx->playerColors[0], 0x0000, 0x0000, 0xAFFF ); + allocAndSet( map, &dctx->playerColors[1], 0xAFFF, 0x0000, 0x0000 ); + allocAndSet( map, &dctx->playerColors[2], 0x0000, 0xAFFF, 0x0000 ); + allocAndSet( map, &dctx->playerColors[3], 0xAFFF, 0x0000, 0xAFFF ); + + allocAndSet( map, &dctx->red, 0xFFFF, 0x0000, 0x0000 ); + } + + return (DrawCtx*)dctx; +} /* gtk_drawctxt_init */ + +#endif /* PLATFORM_GTK */ + diff --git a/xwords4/linux/gtkdraw.h b/xwords4/linux/gtkdraw.h new file mode 100644 index 000000000..8d629b36d --- /dev/null +++ b/xwords4/linux/gtkdraw.h @@ -0,0 +1,27 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make debug"; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _GTKDRAW_H_ +#define _GTKDRAW_H_ + +#include "draw.h" + +DrawCtx* gtkDrawCtxtMake( GtkWidget *widget, GtkAppGlobals* globals ); + +#endif diff --git a/xwords4/linux/gtkletterask.c b/xwords4/linux/gtkletterask.c new file mode 100644 index 000000000..6a88c39e6 --- /dev/null +++ b/xwords4/linux/gtkletterask.c @@ -0,0 +1,108 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ +#ifdef PLATFORM_GTK + +#include + +#include "gtkask.h" + +static void +button_event( GtkWidget* widget, void* closure ) +{ + XP_Bool* whichSet = (XP_Bool*)closure; + *whichSet = 1; + + gtk_main_quit(); +} + +#define BUTTONS_PER_ROW 13 + +void +gtkletterask( DictionaryCtxt* dict, char* resultBuf ) +{ + GtkWidget* dialog; + GtkWidget* label; + Tile tile, nonBlanks; + XP_Bool results[32]; /* MAX NUM FACES */ + unsigned char buf[4]; + GtkWidget* vbox; + GtkWidget* hbox = NULL; + + XP_U16 numFaces = dict_numTileFaces( dict ); + Tile blankFace = dict_getBlankTile( dict ); + + XP_MEMSET( results, 0, sizeof(results) ); + + XP_ASSERT( numFaces > 0 ); + + vbox = gtk_vbox_new( FALSE, 0 ); + + for ( nonBlanks = tile = 0; tile < numFaces; ++tile ) { + GtkWidget* button; + if ( tile == blankFace ) { + continue; + } + + if ( nonBlanks % BUTTONS_PER_ROW == 0 ) { + hbox = gtk_hbox_new( FALSE, 0 ); + } + dict_tilesToString( dict, &nonBlanks, 1, buf ); + button = gtk_button_new_with_label( buf ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0 ); + gtk_signal_connect( GTK_OBJECT(button), "clicked", button_event, + &results[nonBlanks] ); + gtk_widget_show( button ); + + if ( tile+1 == numFaces || (nonBlanks % BUTTONS_PER_ROW == 0) ) { + gtk_widget_show( hbox ); + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + } + ++nonBlanks; + } + gtk_widget_show( vbox ); + + /* Create the widgets */ + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + label = gtk_label_new( "Choose a letter for your blank." ); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label); + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); + gtk_widget_show_all( dialog ); + + gtk_main(); + + gtk_widget_destroy( dialog ); + + for ( nonBlanks = tile = 0; tile < numFaces; ++tile ) { + if ( tile == blankFace ) { + continue; + } else if ( results[nonBlanks] ) { + break; + } + ++nonBlanks; + } + + dict_tilesToString( dict, &tile, 1, resultBuf ); +} /* gtkletterask */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkletterask.h b/xwords4/linux/gtkletterask.h new file mode 100644 index 000000000..f3a72781d --- /dev/null +++ b/xwords4/linux/gtkletterask.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + + +#ifdef PLATFORM_GTK + +#ifndef _GTKLETTERASK_H_ +#define _GTKLETTERASK_H_ + +#include "gtkmain.h" + +void gtkletterask( DictionaryCtxt* dict, char* buf ); + +#endif /* _GTKLETTERASK_H_ */ +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c new file mode 100644 index 000000000..c62004304 --- /dev/null +++ b/xwords4/linux/gtkmain.c @@ -0,0 +1,1537 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000-2003 by Eric House (fixin@peak.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. + */ + +#ifdef PLATFORM_GTK + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#ifndef CLIENT_ONLY +/* # include */ +#endif +#include +#include + +#include "main.h" +#include "linuxmain.h" +/* #include "gtkmain.h" */ + +#include "draw.h" +#include "game.h" +#include "gtkask.h" +#include "gtknewgame.h" +#include "gtkletterask.h" +#include "gtkpasswdask.h" +/* #include "undo.h" */ +#include "gtkdraw.h" +#include "memstream.h" +#include "filestream.h" + +/* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ +static void sendOnClose( XWStreamCtxt* stream, void* closure ); +static XP_Bool file_exists( char* fileName ); +static void gtkListenOnSocket( GtkAppGlobals* globals, int newSock ); +static void setCtrlsForTray( GtkAppGlobals* globals ); +static void printFinalScores( GtkAppGlobals* globals ); + + +#if 0 +static XWStreamCtxt* +lookupClientStream( GtkAppGlobals* globals, int sock ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->sock == sock ) { + XP_ASSERT( rec->stream != NULL ); + return rec->stream; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); + return NULL; +} /* lookupClientStream */ + +static void +rememberClient( GtkAppGlobals* globals, guint key, int sock, + XWStreamCtxt* stream ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->stream == NULL ) { + XP_ASSERT( stream != NULL ); + rec->stream = stream; + rec->key = key; + rec->sock = sock; + break; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); +} /* rememberClient */ +#endif + +static gint +button_press_event( GtkWidget *widget, GdkEventButton *event, + GtkAppGlobals* globals ) +{ + + XP_Bool redraw = board_handlePenDown( globals->cGlobals.game.board, + event->x, event->y, event->time ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + return 1; +} /* button_press_event */ + +static gint +motion_notify_event( GtkWidget *widget, GdkEventMotion *event, + GtkAppGlobals* globals ) +{ + XP_Bool handled; + + handled = board_handlePenMove( globals->cGlobals.game.board, event->x, + event->y ); + if ( handled ) { + board_draw( globals->cGlobals.game.board ); + } + + return handled; +} /* motion_notify_event */ + +static gint +button_release_event( GtkWidget *widget, GdkEventMotion *event, + GtkAppGlobals* globals ) +{ + XP_Bool redraw = board_handlePenUp( globals->cGlobals.game.board, + event->x, + event->y, event->time ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + return 1; +} /* button_release_event */ + +static gint +key_release_event( GtkWidget *widget, GdkEventKey* event, + GtkAppGlobals* globals ) +{ + XP_Key xpkey = XP_KEY_NONE; + guint keyval = event->keyval; + + XP_LOGF( "got key 0x%x", keyval ); + + switch( keyval ) { + + case GDK_Left: + xpkey = XP_CURSOR_KEY_LEFT; + break; + case GDK_Right: + xpkey = XP_CURSOR_KEY_RIGHT; + break; + case GDK_Up: + xpkey = XP_CURSOR_KEY_UP; + break; + case GDK_Down: + xpkey = XP_CURSOR_KEY_DOWN; + break; + case GDK_BackSpace: + XP_LOGF( "... it's a DEL" ); + xpkey = XP_CURSOR_KEY_DEL; + break; + default: + keyval = keyval & 0x00FF; /* mask out gtk stuff */ + if ( isalpha( keyval ) ) { + xpkey = toupper(keyval); + break; + } + return FALSE; + } + + if ( xpkey != XP_KEY_NONE ) { + if ( board_handleKey( globals->cGlobals.game.board, xpkey ) ) { + board_draw( globals->cGlobals.game.board ); + } + } + + return 0; +} /* key_release_event */ + +#ifdef MEM_DEBUG +# define MEMPOOL globals->cGlobals.params->util->mpool, +#else +# define MEMPOOL +#endif + +static XWStreamCtxt* +streamFromFile( GtkAppGlobals* globals, char* name ) +{ + XP_U8* buf; + struct stat statBuf; + FILE* f; + XWStreamCtxt* stream; + + (void)stat( name, &statBuf ); + buf = malloc( statBuf.st_size ); + f = fopen( name, "r" ); + fread( buf, statBuf.st_size, 1, f ); + fclose( f ); + + stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + stream_putBytes( stream, buf, statBuf.st_size ); + free( buf ); + + return stream; +} /* streamFromFile */ + +static void +createOrLoadObjects( GtkAppGlobals* globals, GtkWidget *widget ) +{ + XWStreamCtxt* stream = NULL; + + Connectedness serverRole = globals->cGlobals.params->serverRole; + XP_Bool isServer = serverRole != SERVER_ISCLIENT; + LaunchParams* params = globals->cGlobals.params; + + globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( widget, globals ); + + if ( !!params->fileName && file_exists( params->fileName ) ) { + + stream = streamFromFile( globals, params->fileName ); + + game_makeFromStream( MEMPOOL stream, &globals->cGlobals.game, + &globals->cGlobals.params->gi, + params->dict, params->util, + (DrawCtx*)globals->draw, + &globals->cp, + linux_udp_send, globals ); + + stream_destroy( stream ); + + } else { /* not reading from a saved file */ + CommsAddrRec addr; + + if ( serverRole == SERVER_ISCLIENT ) { + globals->cGlobals.defaultServerName = + params->info.clientInfo.serverName; + } + + params->gi.gameID = util_getCurSeconds(globals->cGlobals.params->util); + XP_STATUSF( "grabbed gameID: %ld\n", params->gi.gameID ); + game_makeNewGame( MEMPOOL &globals->cGlobals.game, ¶ms->gi, + params->util, (DrawCtx*)globals->draw, + &globals->cp, linux_udp_send, globals ); + + addr.conType = COMMS_CONN_IP; + addr.u.ip.ipAddr = 0; /* ??? */ + addr.u.ip.port = params->defaultSendPort; + comms_setAddr( globals->cGlobals.game.comms, + &addr, params->defaultListenPort ); + + model_setDictionary( globals->cGlobals.game.model, params->dict ); + +/* params->gi.phoniesAction = PHONIES_DISALLOW; */ + + if ( !isServer ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL params->vtMgr, globals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } + } + + server_do( globals->cGlobals.game.server ); + +} /* createOrLoadObjects */ + +/* Create a new backing pixmap of the appropriate size and set up contxt to + * draw using that size. + */ +static gint +configure_event( GtkWidget *widget, GdkEventConfigure *event, + GtkAppGlobals* globals ) +{ + short width, height, leftMargin, topMargin; + short timerLeft, timerTop; + gboolean firstTime; + gint hscale, vscale; + gint trayTop; + gint boardTop = 0; + + firstTime = (globals->draw == NULL); + + if ( firstTime ) { + int listenSocket = + initListenerSocket(globals->cGlobals.params->defaultListenPort); + gtkListenOnSocket( globals, listenSocket ); + + createOrLoadObjects( globals, widget ); + } else { +/* gdk_pixmap_unref( ((GtkDrawCtx*)globals->draw)->pixmap ); */ + } + + width = widget->allocation.width - (RIGHT_MARGIN + BOARD_LEFT_MARGIN); + height = widget->allocation.height - (TOP_MARGIN + BOTTOM_MARGIN) + - MIN_TRAY_SCALEV - BOTTOM_MARGIN; + + hscale = width / NUM_COLS; + vscale = (height / (NUM_ROWS + 2)); /* makd tray height 2x cell height */ + + leftMargin = (widget->allocation.width - (hscale*NUM_COLS)) / 2; + topMargin = (widget->allocation.height - (vscale*(NUM_ROWS*2))) / 2; + + + if ( !globals->cGlobals.params->verticalScore ) { + boardTop += HOR_SCORE_HEIGHT; + } + + trayTop = boardTop + (vscale * NUM_ROWS) + BOTTOM_MARGIN + 1; + if ( globals->cGlobals.params->trayOverlaps ) { + trayTop -= vscale * 2; + } + board_setPos( globals->cGlobals.game.board, BOARD_LEFT, boardTop, + XP_FALSE ); + board_setScale( globals->cGlobals.game.board, hscale, vscale ); + board_setShowColors( globals->cGlobals.game.board, XP_TRUE ); + globals->gridOn = XP_TRUE; + + if ( globals->cGlobals.params->verticalScore ) { + board_setScoreboardLoc( globals->cGlobals.game.board, + BOARD_LEFT + (MIN_SCALE*NUM_COLS) + 1, + VERT_SCORE_TOP, + VERT_SCORE_WIDTH, + VERT_SCORE_HEIGHT, + XP_FALSE ); + + timerLeft = BOARD_LEFT + (MIN_SCALE*NUM_COLS) + 1; + timerTop = TIMER_TOP; + + } else { + board_setScoreboardLoc( globals->cGlobals.game.board, + BOARD_LEFT, HOR_SCORE_TOP, + HOR_SCORE_WIDTH, HOR_SCORE_HEIGHT, + XP_TRUE ); + + timerLeft = BOARD_LEFT + (MIN_SCALE*NUM_COLS) + 1; + timerTop = TIMER_TOP; + } + + board_setTimerLoc( globals->cGlobals.game.board, timerLeft, timerTop, + TIMER_WIDTH, HOR_SCORE_HEIGHT ); + + board_setTrayLoc( globals->cGlobals.game.board, TRAY_LEFT, trayTop, + hscale * 2, vscale * 2, + GTK_DIVIDER_WIDTH ); + + setCtrlsForTray( globals ); + + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + + return TRUE; +} /* configure_event */ + +/* Redraw the screen from the backing pixmap */ +static gint +expose_event( GtkWidget *widget, + GdkEventExpose *event, + GtkAppGlobals* globals ) +{ + /* + gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height+widget->allocation.y ); + */ + board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); + +/* board_invalAll( globals->cGlobals.game.board ); */ + board_draw( globals->cGlobals.game.board ); + +/* gdk_draw_pixmap( widget->window, */ +/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */ +/* ((GtkDrawCtx*)globals->draw)->pixmap, */ +/* event->area.x, event->area.y, */ +/* event->area.x, event->area.y, */ +/* event->area.width, event->area.height ); */ + + return FALSE; +} /* expose_event */ + +#if 0 +static gint +handle_client_event( GtkWidget *widget, GdkEventClient *event, + GtkAppGlobals* globals ) +{ + XP_LOGF( "handle_client_event called: event->type = " ); + if ( event->type == GDK_CLIENT_EVENT ) { + XP_LOGF( "GDK_CLIENT_EVENT" ); + return 1; + } else { + XP_LOGF( "%d", event->type ); + return 0; + } +} /* handle_client_event */ +#endif + +static void +writeToFile( XWStreamCtxt* stream, void* closure ) +{ + void* buf; + FILE* file; + XP_U16 len; + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + + len = stream_getSize( stream ); + buf = malloc( len ); + stream_getBytes( stream, buf, len ); + + file = fopen( globals->cGlobals.params->fileName, "w" ); + fwrite( buf, 1, len, file ); + fclose( file ); + + free( buf ); +} /* writeToFile */ + +static void +quit( void* dunno, GtkAppGlobals* globals ) +{ + if ( !!globals->cGlobals.params->fileName ) { + XWStreamCtxt* outStream; + + outStream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, + globals, 0, writeToFile ); + stream_open( outStream ); + + game_saveToStream( &globals->cGlobals.game, + &globals->cGlobals.params->gi, + outStream ); + + stream_destroy( outStream ); + } + + game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ + gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi ); + + vtmgr_destroy( MEMPOOL globals->cGlobals.params->vtMgr ); + + mpool_destroy( globals->cGlobals.params->util->mpool ); + + gtk_exit( 0 ); +} /* quit */ + +GtkWidget* +makeAddSubmenu( GtkWidget* menubar, gchar* label ) +{ + GtkWidget* submenu; + GtkWidget* item; + + item = gtk_menu_item_new_with_label( label ); + gtk_menu_bar_append( GTK_MENU_BAR(menubar), item ); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); + + gtk_widget_show(item); + + return submenu; +} /* makeAddSubmenu */ + +static void +tile_values( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( !!globals->cGlobals.game.server ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + catOnClose ); + server_formatPoolCounts( globals->cGlobals.game.server, stream, 5 ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); + } + +} /* tile_values */ + +static void +game_history( GtkWidget* widget, GtkAppGlobals* globals ) +{ + catGameHistory( &globals->cGlobals ); +} /* game_history */ + +static void +final_scores( GtkWidget* widget, GtkAppGlobals* globals ) +{ + XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); + + if ( gameOver ) { + printFinalScores( globals ); + } else { + XP_Bool confirmed; + confirmed = + gtkask( globals, + "Are you sure everybody wants to end the game now?", + 2, "Yes", "No" ) == 0; + + if ( confirmed ) { + server_endGame( globals->cGlobals.game.server ); + gameOver = TRUE; + } + } + + /* the end game listener will take care of printing the final scores */ +} /* final_scores */ + +static void +new_game( GtkWidget* widget, GtkAppGlobals* globals ) +{ + gboolean confirmed; + + confirmed = newGameDialog( globals ); + if ( confirmed ) { + CurGameInfo* gi = &globals->cGlobals.params->gi; + XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; + XP_U32 gameID = util_getCurSeconds( globals->cGlobals.params->util ); + + XP_STATUSF( "grabbed gameID: %ld\n", gameID ); + game_reset( MEMPOOL &globals->cGlobals.game, gi, gameID, &globals->cp, + linux_udp_send, globals ); + + if ( isClient ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } + + (void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */ + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + } + +} /* new_game */ + +static void +load_game( GtkWidget* widget, GtkAppGlobals* globals ) +{ +} /* load_game */ + +static void +save_game( GtkWidget* widget, GtkAppGlobals* globals ) +{ +} /* save_game */ + +static void +load_dictionary( GtkWidget* widget, GtkAppGlobals* globals ) +{ +} /* load_dictionary */ + +static void +handle_undo( GtkWidget* widget, GtkAppGlobals* globals ) +{ +} /* handle_undo */ + +static void +handle_redo( GtkWidget* widget, GtkAppGlobals* globals ) +{ +} /* handle_redo */ + +static void +handle_resend( GtkWidget* widget, GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + comms_resendAll( comms ); +} /* handle_resend */ + +#ifdef DEBUG +static void +handle_commstats( GtkWidget* widget, GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + + if ( !!comms ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + comms_getStats( comms, stream ); + stream_destroy( stream ); + } +} /* handle_commstats */ +#endif + +#ifdef MEM_DEBUG +static void +handle_memstats( GtkWidget* widget, GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + mpool_stats( MEMPOOL stream ); + stream_destroy( stream ); + +} /* handle_memstats */ +#endif + +static GtkWidget* +createAddItem( GtkWidget* parent, gchar* label, + GtkSignalFunc handlerFunc, GtkAppGlobals* globals ) +{ + GtkWidget* item = gtk_menu_item_new_with_label( label ); + +/* g_print( "createAddItem called with label %s\n", label ); */ + + if ( handlerFunc != NULL ) { + gtk_signal_connect( GTK_OBJECT(item), "activate", + GTK_SIGNAL_FUNC(handlerFunc), globals ); + } + + gtk_menu_append( GTK_MENU(parent), item ); + gtk_widget_show( item ); + + return item; +} /* createAddItem */ + +static GtkWidget* +makeMenus( GtkAppGlobals* globals, int argc, char** argv ) +{ + GtkWidget* menubar = gtk_menu_bar_new(); + GtkWidget* fileMenu; + + fileMenu = makeAddSubmenu( menubar, "File" ); + (void)createAddItem( fileMenu, "Tile values", + GTK_SIGNAL_FUNC(tile_values), globals ); + (void)createAddItem( fileMenu, "Game history", + GTK_SIGNAL_FUNC(game_history), globals ); + + (void)createAddItem( fileMenu, "Final scores", + GTK_SIGNAL_FUNC(final_scores), globals ); + + (void)createAddItem( fileMenu, "New game", + GTK_SIGNAL_FUNC(new_game), globals ); + + (void)createAddItem( fileMenu, "Load game", + GTK_SIGNAL_FUNC(load_game), globals ); + (void)createAddItem( fileMenu, "Save game", + GTK_SIGNAL_FUNC(save_game), globals ); + + (void)createAddItem( fileMenu, "Load dictionary", + GTK_SIGNAL_FUNC(load_dictionary), globals ); + + fileMenu = makeAddSubmenu( menubar, "Edit" ); + + (void)createAddItem( fileMenu, "Undo", + GTK_SIGNAL_FUNC(handle_undo), globals ); + (void)createAddItem( fileMenu, "Redo", + GTK_SIGNAL_FUNC(handle_redo), globals ); + + fileMenu = makeAddSubmenu( menubar, "Network" ); + + (void)createAddItem( fileMenu, "Resend", + GTK_SIGNAL_FUNC(handle_resend), globals ); +#ifdef DEBUG + (void)createAddItem( fileMenu, "Stats", + GTK_SIGNAL_FUNC(handle_commstats), globals ); +#endif +#ifdef MEM_DEBUG + (void)createAddItem( fileMenu, "Mem stats", + GTK_SIGNAL_FUNC(handle_memstats), globals ); +#endif + +/* (void)createAddItem( fileMenu, "Print board", */ +/* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ + +/* listAllGames( menubar, argc, argv, globals ); */ + + gtk_widget_show( menubar ); + + return menubar; +} /* makeMenus */ + +static void +handle_flip_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( board_flip( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_flip_button */ + +static void +handle_value_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_value_button */ + +static void +handle_hint_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + XP_Bool redo; + if ( board_requestHint( globals->cGlobals.game.board, &redo ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_hint_button */ + +static void +handle_colors_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ +/* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ +/* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ +/* board_draw( globals->cGlobals.game.board ); */ +/* } */ +} /* handle_colors_button */ + +static void +handle_juggle_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( board_juggleTray( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_juggle_button */ + +static void +handle_undo_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( server_handleUndo( globals->cGlobals.game.server ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_undo_button */ + +static void +handle_redo_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ +} /* handle_redo_button */ + +static void +handle_trade_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( board_beginTrade( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_juggle_button */ + +static void +scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) +{ + XP_U16 curYOffset, newValue; + gfloat newValueF = adj->value; + + XP_ASSERT( newValueF >= 0.0 && newValueF <= 2.0 ); + curYOffset = board_getYOffset( globals->cGlobals.game.board ); + newValue = (XP_U16)newValueF; + + if ( newValue != curYOffset ) { + board_setYOffset( globals->cGlobals.game.board, newValue, XP_FALSE ); + board_draw( globals->cGlobals.game.board ); + } +} /* scroll_value_changed */ + +static void +handle_grid_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + XP_U16 scaleH, scaleV; + XP_Bool gridOn = globals->gridOn; + + board_getScale( globals->cGlobals.game.board, &scaleH, &scaleV ); + + if ( gridOn ) { + --scaleH; + --scaleV; + } else { + ++scaleH; + ++scaleV; + } + + board_setScale( globals->cGlobals.game.board, scaleH, scaleV ); + globals->gridOn = !gridOn; + + board_draw( globals->cGlobals.game.board ); +} /* handle_grid_button */ + +static void +handle_hide_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( globals->cGlobals.params->trayOverlaps ) { + globals->adjustment->page_size = MAX_ROWS; + globals->adjustment->value = 0.0; + + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); + } +/* board_setTrayVisible( globals->board, XP_FALSE, XP_TRUE ); */ + if ( board_hideTray( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_hide_button */ + +static void +handle_commit_button( GtkWidget* widget, GtkAppGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_commit_button */ + +static void +gtkUserError( GtkAppGlobals* globals, char* format, ... ) +{ + char buf[512]; + va_list ap; + + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + (void)gtkask( globals, buf, 1, "OK" ); + + va_end(ap); +} /* gtkUserError */ + +static VTableMgr* +gtk_util_getVTManager(XW_UtilCtxt* uc) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + return globals->cGlobals.params->vtMgr; +} /* linux_util_getVTManager */ + +static void +gtk_util_askBlankFace(XW_UtilCtxt* uc, DictionaryCtxt* dict, XP_UCHAR* buf ) +{ + gtkletterask( dict, buf ); +} /* gtk_util_askBlankFace */ + +static XP_Bool +gtk_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + XP_Bool ok = gtkpasswdask( name, buf, len ); + return ok; +} /* gtk_util_askPassword */ + +static void +setCtrlsForTray( GtkAppGlobals* globals ) +{ + XW_TrayVisState state = + board_getTrayVisState( globals->cGlobals.game.board ); + if ( globals->cGlobals.params->trayOverlaps ) { + globals->adjustment->page_size = state==TRAY_HIDDEN? 15 : 13; + if ( state != TRAY_HIDDEN ) { /* do we need to adjust scrollbar? */ + globals->adjustment->value = + board_getYOffset( globals->cGlobals.game.board ); + } + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + } +} /* setCtrlsForTray */ + +static void +gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState state ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + setCtrlsForTray( globals ); +} /* gtk_util_trayHiddenChange */ + +static void +gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset, XP_U16 newOffset ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + board_invalAll( globals->cGlobals.game.board ); +/* board_draw( globals->board ); */ +} /* gtk_util_trayHiddenChange */ + +static void +printFinalScores( GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream; + + stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, catOnClose ); + server_writeFinalScores( globals->cGlobals.game.server, stream ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); +} /* printFinalScores */ + +static void +gtk_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + if ( globals->cGlobals.params->printHistory ) { + catGameHistory( &globals->cGlobals ); + } + + printFinalScores( globals ); + + if ( globals->cGlobals.params->quitAfter ) { + quit( NULL, globals ); + } else if ( globals->cGlobals.params->undoWhenDone ) { + server_handleUndo( globals->cGlobals.game.server ); + } + + board_draw( globals->cGlobals.game.board ); +} /* gtk_util_notifyGameOver */ + +/* define this to prevent user events during debugging from stopping the engine */ +/* #define DONT_ABORT_ENGINE */ + +static XP_Bool +gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + gboolean pending; + + board_hiliteCellAt( globals->cGlobals.game.board, col, row ); + if ( globals->cGlobals.params->sleepOnAnchor ) { + } + +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + pending = gdk_events_pending(); + return !pending; +#endif +} /* gtk_util_hiliteCell */ + +static XP_Bool +gtk_util_engineProgressCallback( XW_UtilCtxt* uc ) +{ +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + gboolean pending = gdk_events_pending(); + +/* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ + + return !pending; +#endif +} /* gtk_util_engineProgressCallback */ + +static gint +pentimer_idle_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + struct timeval tv; + XP_Bool callAgain = XP_TRUE; + + XP_ASSERT( gettimeofday( &tv, NULL ) == 0 ); + + if ( (tv.tv_usec - globals->penTv.tv_usec) >= globals->penTimerInterval) { + board_timerFired( globals->cGlobals.game.board, TIMER_PENDOWN ); + callAgain = XP_FALSE; + } + + return callAgain; +} /* timer_idle_func */ + +static gint +score_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + XP_Bool callAgain = XP_TRUE; + + board_timerFired( globals->cGlobals.game.board, TIMER_TIMERTICK ); + callAgain = XP_FALSE; + + return callAgain; +} /* score_timer_func */ + +static void +gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + if ( why == TIMER_PENDOWN ) { + globals->penTimerInterval = 35 * 10000; + (void)gettimeofday( &globals->penTv, NULL ); + (void)gtk_idle_add( pentimer_idle_func, globals ); + } else if ( why == TIMER_TIMERTICK ) { + globals->scoreTimerInterval = 100 * 10000; + (void)gettimeofday( &globals->scoreTv, NULL ); + + (void)gtk_timeout_add( 1000, score_timer_func, globals ); + } + +} /* gtk_util_setTimer */ + +static gint +idle_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; +/* XP_DEBUGF( "idle_func called\n" ); */ + + /* remove before calling server_do. If server_do puts up a dialog that + calls gtk_main, then this idle proc will also apply to that event loop + and bad things can happen. So kill the idle proc asap. */ + gtk_idle_remove( globals->idleID ); + + if ( server_do( globals->cGlobals.game.server ) ) { + XP_ASSERT( globals->cGlobals.game.board != NULL ); + board_draw( globals->cGlobals.game.board ); + } + return 0; /* 0 will stop it from being called again */ +} /* idle_func */ + +static void +gtk_util_requestTime( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + globals->idleID = gtk_idle_add( idle_func, globals ); +} /* gtk_util_requestTime */ + +static XP_Bool +gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, + XP_Bool turnLost ) +{ + XP_Bool result; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + char buf[300]; + + if ( turnLost ) { + char wordsBuf[256]; + XP_U16 i; + XP_UCHAR* name = globals->cGlobals.params->gi.players[player].name; + XP_ASSERT( !!name ); + + for ( i = 0, wordsBuf[0] = '\0'; ; ) { + char wordBuf[18]; + sprintf( wordBuf, "\"%s\"", bwi->words[i] ); + strcat( wordsBuf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + strcat( wordsBuf, ", " ); + } + + sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", + player+1, name, wordsBuf ); + + if ( globals->cGlobals.params->skipWarnings ) { + XP_LOGF( "%s", buf ); + } else { + gtkUserError( globals, buf ); + } + result = XP_TRUE; + } else { + XP_ASSERT( bwi->nWords == 1 ); + sprintf( buf, "Word \"%s\" not in the current dictionary. " + "Use it anyway?", bwi->words[0] ); + result = 0 == gtkask( globals, buf, 2, "Ok", "Cancel" ); + } + + return result; +} /* gtk_util_warnIllegalWord */ + +static XWStreamCtxt* +gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + uc->closure, channelNo, + sendOnClose ); + return stream; +} /* gtk_util_makeStreamFromAddr */ + +static void +gtk_util_listenPortChange( XW_UtilCtxt* uc, XP_U16 newPort ) +{ +#ifdef DEBUG + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; +#endif + XP_LOGF( "listenPortChange called: not sure what to do" ); + + /* if this isn't true, need to tear down and rebind socket */ + XP_ASSERT( newPort == globals->cGlobals.params->defaultListenPort ); +} /* gtk_util_getListeningPort */ + +static void +gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_UCHAR* message = linux_getErrString( id ); + + gtkUserError( globals, message ); +} /* gtk_util_userError */ + +static XP_U16 +gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + XP_U16 result; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + char* question; + char* answers[3]; + gint numAnswers = 0; + XP_Bool freeMe = XP_FALSE; + + switch( id ) { + + case QUERY_COMMIT_TURN: + question = strFromStream( stream ); + freeMe = XP_TRUE; +/* len = stream_getSize( stream ); */ +/* question = malloc( len + 1 ); */ +/* stream_getBytes( stream, question, len ); */ +/* question[len] = '\0'; */ + answers[numAnswers++] = "Cancel"; + answers[numAnswers++] = "Ok"; + break; + case QUERY_COMMIT_TRADE: + question = "Are you sure you want to trade the selected tiles?"; + answers[numAnswers++] = "Cancel"; + answers[numAnswers++] = "Ok"; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + question = strFromStream( stream ); + freeMe = XP_TRUE; + answers[numAnswers++] = "Ok"; + break; + + default: + XP_ASSERT( 0 ); + return 0; + } + + result = gtkask( globals, question, numAnswers, + answers[0], answers[1], answers[2] ); + + if ( freeMe > 0 ) { + free( question ); + } + + return result; +} /* gtk_util_userQuery */ + +static XP_Bool +file_exists( char* fileName ) +{ + struct stat statBuf; + + int statResult = stat( fileName, &statBuf ); + return statResult == 0; +} /* file_exists */ + +static GtkWidget* +makeShowButtonFromBitmap( GtkAppGlobals* globals, GtkWidget* parent, + char* fileName, char* alt, GtkSignalFunc func ) +{ + GtkWidget* button; + GtkWidget* pixmapWid; + GdkPixmap* pixmap; + GdkBitmap *mask; + GtkStyle *style; + + if ( file_exists( fileName ) ) { + button = gtk_button_new(); + + style = gtk_widget_get_style(parent); + + pixmap = gdk_pixmap_create_from_xpm( parent->window, &mask, + &style->bg[GTK_STATE_NORMAL], + fileName ); + pixmapWid = gtk_pixmap_new( pixmap, mask ); + gtk_container_add( GTK_CONTAINER(button), pixmapWid ); + + gtk_widget_show( pixmapWid ); + } else { + button = gtk_button_new_with_label( alt ); + } + gtk_widget_show( button ); + + if ( func != NULL ) { + gtk_signal_connect( GTK_OBJECT(button), "clicked", func, globals ); + } + + return button; +} /* makeShowButtonFromBitmap */ + +static GtkWidget* +makeVerticalBar( GtkAppGlobals* globals, GtkWidget* window ) +{ + GtkWidget* vbox; + GtkWidget* button; + GtkWidget* vscrollbar; + + vbox = gtk_vbox_new( FALSE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../flip.xpm", "f", + handle_flip_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../value.xpm", "v", + handle_value_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../hint.xpm", "?", + handle_hint_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../colors.xpm", "c", + handle_colors_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + if ( globals->cGlobals.params->trayOverlaps ) { + globals->adjustment = (GtkAdjustment*)gtk_adjustment_new( 0, 0, 15, + 1, 2, 13 ); + vscrollbar = gtk_vscrollbar_new( globals->adjustment ); + gtk_signal_connect( GTK_OBJECT(globals->adjustment), "value_changed", + GTK_SIGNAL_FUNC(scroll_value_changed), globals ); + + gtk_widget_show( vscrollbar ); + gtk_box_pack_start( GTK_BOX(vbox), vscrollbar, TRUE, TRUE, 0 ); + } + + /* undo and redo buttons */ + button = makeShowButtonFromBitmap( globals, window, "../undo.xpm", "u", + handle_undo_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, window, "../redo.xpm", "r", + handle_redo_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* the four buttons that on palm are beside the tray */ + button = makeShowButtonFromBitmap( globals, window, "../juggle.xpm", "j", + handle_juggle_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../trade.xpm", "t", + handle_trade_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../hide.xpm", "h", + handle_hide_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, window, "../hide.xpm", "d", + handle_commit_button ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + + + gtk_widget_show( vbox ); + return vbox; +} /* makeVerticalBar */ + +static GtkWidget* +makeButtons( GtkAppGlobals* globals, int argc, char** argv ) +{ + short i; + GtkWidget* hbox; + GtkWidget* button; + + struct { + char* name; + GtkSignalFunc func; + } buttons[] = { + /* { "Flip", handle_flip_button }, */ + { "Grid", handle_grid_button }, + { "Hide", handle_hide_button }, + { "Commit", handle_commit_button }, + }; + + hbox = gtk_hbox_new( FALSE, 0 ); + + for ( i = 0; i < sizeof(buttons)/sizeof(*buttons); ++i ) { + button = gtk_button_new_with_label( buttons[i].name ); + gtk_widget_show( button ); + gtk_signal_connect( GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(buttons[i].func), globals ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); + } + + gtk_widget_show( hbox ); + return hbox; +} /* makeButtons */ + +static void +setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) +{ + util->vtable->m_util_userError = gtk_util_userError; + util->vtable->m_util_userQuery = gtk_util_userQuery; + util->vtable->m_util_getVTManager = gtk_util_getVTManager; + util->vtable->m_util_askBlankFace = gtk_util_askBlankFace; + util->vtable->m_util_askPassword = gtk_util_askPassword; + util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; + util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; + util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; + util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; + util->vtable->m_util_engineProgressCallback = + gtk_util_engineProgressCallback; + util->vtable->m_util_setTimer = gtk_util_setTimer; + util->vtable->m_util_requestTime = gtk_util_requestTime; + util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord; + + util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; +#ifdef BEYOND_IR + util->vtable->m_util_listenPortChange = gtk_util_listenPortChange; +#endif + + util->closure = globals; +} /* setupGtkUtilCallbacks */ + +static void +newConnectionInput( gpointer data, gint source, + GdkInputCondition condition ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + struct sockaddr_in addr_sock; + + int flen, nBytes; + char buf[256]; + char* bufPtr = buf; + + XP_LOGF( "Incomming message!!!" ); + + memset( buf, 0, sizeof(buf) ); + flen = sizeof(struct sockaddr_in); + nBytes = recvfrom( source, buf, sizeof(buf), 0, + (struct sockaddr *)&addr_sock, &flen ); + XP_ASSERT( nBytes != -1 ); + + if ( !globals->dropIncommingMsgs && nBytes != -1 ) { + XWStreamCtxt* inboundS; + XP_Bool redraw = XP_FALSE; + + /* somehow, we need to associate a return address with a client. This + is the address we'll be using from now on. So when a connection is + made, pass the return address to the comms module. It can decide + from the message if it's seen the message source before.*/ + + /* inboundS = linux_make_inbound_socketStream( */ + /* inboundSocket, &returnSockAddr, addrLen ); */ + + inboundS = stream_from_msgbuf( &globals->cGlobals, bufPtr, nBytes ); + if ( !!inboundS ) { + CommsAddrRec addrRec; + + XP_MEMSET( &addrRec, 0, sizeof(addrRec) ); + addrRec.conType = COMMS_CONN_IP; + + addrRec.u.ip.ipAddr = ntohl(addr_sock.sin_addr.s_addr); + XP_LOGF( "captured incomming ip address: 0x%lx", + addrRec.u.ip.ipAddr ); + + if ( comms_checkIncommingStream( globals->cGlobals.game.comms, + inboundS, &addrRec ) ) { + redraw = server_receiveMessage( globals->cGlobals.game.server, + inboundS ); + } + stream_destroy( inboundS ); + } + + /* if there's something to draw resulting from the message, we need to + give the main loop time to reflect that on the screen before giving + the server another shot. So just call the idle proc. */ + if ( redraw ) { + gtk_util_requestTime( globals->cGlobals.params->util ); + } else { + redraw = server_do( globals->cGlobals.game.server ); + } + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + } else { + XP_LOGF( "errno from accept: %d", errno ); + } + +} /* newConnectionInput */ + +static void +newConnectionDestroy( gpointer data ) +{ + XP_LOGF( "newConnectionDestroy called" ); +} /* newConnectionDestroy */ + +/* Make gtk listen for events on the socket that clients will use to + * connect to us. + */ +static void +gtkListenOnSocket( GtkAppGlobals* globals, int newSock ) +{ + guint result = gtk_input_add_full( newSock, + GDK_INPUT_READ, + newConnectionInput, + (GtkCallbackMarshal)0L, + globals, + newConnectionDestroy ); + globals->listenSockKey = result; +} /* gtkListenOnSocket */ + +static void +sendOnClose( XWStreamCtxt* stream, void* closure ) +{ + XP_S16 result; + GtkAppGlobals* globals = closure; + + XP_LOGF( "sendOnClose called" ); + result = comms_send( globals->cGlobals.game.comms, COMMS_CONN_IP, stream ); +} /* sendOnClose */ + +static void +drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) +{ + globals->dropIncommingMsgs = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(toggle) ); +} /* drop_msg_toggle */ + +int +gtkmain( XP_Bool isServer, LaunchParams* params, int argc, char *argv[] ) +{ + short width, height; + GtkWidget* window; + GtkWidget* drawing_area; + GtkWidget* menubar; + GtkWidget* buttonbar; + GtkWidget* vbox; + GtkWidget* hbox; + GtkWidget* vertBar; + GtkAppGlobals globals; + GtkWidget* dropCheck; + + memset( &globals, 0, sizeof(globals) ); + + globals.cGlobals.params = params; + + globals.cp.showBoardArrow = XP_TRUE; + globals.cp.showRobotScores = params->showRobotScores; + + setupGtkUtilCallbacks( &globals, params->util ); + +/* globals.dictionary = params->dict; */ +/* globals.trayOverlaps = params->trayOverlaps; */ +/* globals.askNewGame = params->askNewGame; */ +/* globals.quitWhenDone = params->quitAfter; */ +/* globals.sleepOnAnchor = params->sleepOnAnchor; */ +/* globals.util = params->util; */ +/* globals.fileName = params->fileName; */ + +/* globals.listenPort = params->listenPort; */ + + /* Now set up the gtk stuff. This is necessary before we make the + draw ctxt */ + gtk_init( &argc, &argv ); + + globals.window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + if ( !!params->fileName ) { + gtk_window_set_title( GTK_WINDOW(window), params->fileName ); + } + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add( GTK_CONTAINER(window), vbox ); + gtk_widget_show( vbox ); + + gtk_signal_connect( GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC (quit), &globals ); + + menubar = makeMenus( &globals, argc, argv ); + gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + + dropCheck = gtk_check_button_new_with_label( "drop incomming messages" ); + gtk_signal_connect(GTK_OBJECT(dropCheck), + "toggled", GTK_SIGNAL_FUNC(drop_msg_toggle), &globals ); + gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); + gtk_widget_show( dropCheck ); + + buttonbar = makeButtons( &globals, argc, argv ); + gtk_box_pack_start( GTK_BOX(vbox), buttonbar, FALSE, TRUE, 0); + + vertBar = makeVerticalBar( &globals, window ); + + drawing_area = gtk_drawing_area_new(); + +#if 0 + width = (MAX_COLS * MIN_SCALE) + LEFT_MARGIN + RIGHT_MARGIN; + height = (MAX_ROWS * MIN_SCALE) + TOP_MARGIN + BOTTOM_MARGIN + + MIN_TRAY_SCALE + BOTTOM_MARGIN; +#else + width = 180; + height = 196; + if ( !globals.cGlobals.params->trayOverlaps ) { + height += MIN_SCALE * 2; + } +#endif + gtk_drawing_area_size( GTK_DRAWING_AREA (drawing_area), + width, height ); + + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start (GTK_BOX (hbox), drawing_area, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), vertBar, TRUE, TRUE, 0); + gtk_widget_show( hbox ); + gtk_widget_show( drawing_area ); + + gtk_box_pack_start (GTK_BOX (vbox), hbox/* drawing_area */, TRUE, TRUE, 0); + + gtk_signal_connect( GTK_OBJECT(drawing_area), "expose_event", + (GtkSignalFunc) expose_event, &globals ); + gtk_signal_connect( GTK_OBJECT(drawing_area),"configure_event", + (GtkSignalFunc) configure_event, &globals ); + gtk_signal_connect( GTK_OBJECT(drawing_area), "button_press_event", + (GtkSignalFunc)button_press_event, &globals ); + gtk_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event", + (GtkSignalFunc)motion_notify_event, &globals ); + gtk_signal_connect( GTK_OBJECT(drawing_area), "button_release_event", + (GtkSignalFunc)button_release_event, &globals ); + + gtk_signal_connect( GTK_OBJECT(window), "key_release_event", + GTK_SIGNAL_FUNC(key_release_event), &globals ); + + gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK +/* | GDK_POINTER_MOTION_HINT_MASK */ + ); + + gtk_widget_show( window ); + + gtk_main(); + +/* MONCONTROL(1); */ + + return 0; +} /* gtkmain */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h new file mode 100644 index 000000000..c3cdd8061 --- /dev/null +++ b/xwords4/linux/gtkmain.h @@ -0,0 +1,121 @@ +/* Copyright 1997 - 2002 by Eric House (fixin@peak.org) (fixin@peak.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. + */ + +#ifndef _GTKMAIN_H_ +#define _GTKMAIN_H_ + +#ifdef PLATFORM_GTK +#include +#include + +#include "draw.h" +#include "main.h" +#include "game.h" +#include "dictnry.h" + +typedef struct GtkDrawCtx { + DrawCtxVTable* vtable; + +/* GdkDrawable* pixmap; */ + GtkWidget* widget; + struct GtkAppGlobals* globals; + + GdkGC* drawGC; + + GdkColor black; + GdkColor white; + GdkColor red; /* for pending tiles */ + GdkColor bonusColors[4]; + GdkColor playerColors[MAX_NUM_PLAYERS]; + GdkFont *gdkFont; + GdkFont *gdkBoldFont; + GdkFont *gdkTrayFont; + + gint trayFontHeight; +} GtkDrawCtx; + +typedef struct ClientStreamRec { + XWStreamCtxt* stream; + guint key; + int sock; +} ClientStreamRec; + +typedef struct GtkAppGlobals { + CommonGlobals cGlobals; + GtkWidget* window; + GtkDrawCtx* draw; + + EngineCtxt* engine; + + guint idleID; + + struct timeval penTv; /* for timer */ + XP_U32 penTimerInterval; + struct timeval scoreTv; /* for timer */ + XP_U32 scoreTimerInterval; + + GtkAdjustment* adjustment; + + ClientStreamRec clientRecs[MAX_NUM_PLAYERS]; + guint listenSockKey; /* save return from gtk_input_add_full */ + + CommonPrefs cp; + + XP_Bool gridOn; + XP_Bool dropIncommingMsgs; +} GtkAppGlobals; + +/* DictionaryCtxt* gtk_dictionary_make(); */ +int gtkmain( XP_Bool isServer, LaunchParams* params, int argc, char *argv[] ); + +#define NUM_COLS 15 +#define NUM_ROWS 15 +#define MIN_SCALE 12 /* was 14 */ + +#define MIN_TRAY_SCALEH 24 +#define MIN_TRAY_SCALEV MIN_TRAY_SCALEH +#define GTK_TRAYPAD_WIDTH 2 + +#define TOP_MARGIN 0 /* was 2 */ +#define BOARD_LEFT_MARGIN 2 +#define TRAY_LEFT_MARGIN 2 +#define SCORE_BOARD_PADDING 0 + +#define HOR_SCORE_LEFT (BOARD_LEFT_MARGIN) +#define HOR_SCORE_HEIGHT 8 +#define TIMER_HEIGHT HOR_SCORE_HEIGHT +#define HOR_SCORE_TOP (TOP_MARGIN) +#define TIMER_PAD 10 +#define VERT_SCORE_TOP (TIMER_HEIGHT + TIMER_PAD) +#define VERT_SCORE_HEIGHT ((MIN_SCALE*MAX_COLS) - TIMER_HEIGHT - TIMER_PAD) +#define TIMER_WIDTH 40 +#define TIMER_TOP HOR_SCORE_TOP +#define HOR_SCORE_WIDTH ((MIN_SCALE*MAX_COLS)-TIMER_PAD) +#define VERT_SCORE_WIDTH 30 + +#define BOARD_TOP (SCORE_TOP + SCORE_HEIGHT + SCORE_BOARD_PADDING ) +#define BOARD_LEFT (BOARD_LEFT_MARGIN) + +#define TRAY_LEFT TRAY_LEFT_MARGIN + +#define GTK_DIVIDER_WIDTH 5 + +#define BOTTOM_MARGIN TOP_MARGIN +#define RIGHT_MARGIN BOARD_LEFT_MARGIN + +#endif /* PLATFORM_GTK */ + +#endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c new file mode 100644 index 000000000..e2344c09a --- /dev/null +++ b/xwords4/linux/gtknewgame.c @@ -0,0 +1,436 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ +#ifdef PLATFORM_GTK + +#include + +#include "gtknewgame.h" +#include "strutils.h" + +#define MAX_SIZE_CHOICES 10 + +/* make the appropriate set of entries sensitive or not + */ +typedef struct ItemNumPair { + GtkWidget* item; + short index; + gboolean found; +} ItemNumPair; + +void +countBeforeSame( GtkWidget *widget, gpointer data ) +{ + ItemNumPair* pair = (ItemNumPair*)data; + if ( !pair->found ) { + if ( pair->item == widget ) { + pair->found = TRUE; + } else { + ++pair->index; + } + } +} /* countBeforeSame */ + +static void +setChildrenSensitivity( GtkWidget* hbox, gboolean enabling ) +{ + gtk_widget_set_sensitive( hbox, enabling ); +} /* setChildrenSensitivity */ + +static void +nplayers_menu_select( GtkWidget* item, GtkNewGameState* state ) +{ + short prevNPlayers = state->nPlayers; + short newNPlayers; + GtkWidget* parent = item->parent; + + ItemNumPair pair; + short high, low; + gboolean enabling; + + pair.item = item; + pair.index = 0; + pair.found = FALSE; + + gtk_container_foreach( GTK_CONTAINER(parent), countBeforeSame, &pair ); + + newNPlayers = pair.index + 1; + + low = XP_MIN( newNPlayers, prevNPlayers ); + high = XP_MAX( newNPlayers, prevNPlayers ); + enabling = newNPlayers > prevNPlayers; + + /* now loop through all the hboxes */ + while ( low < high ) { + setChildrenSensitivity( state->playerEntries[low], enabling ); + ++low; + } + state->nPlayers = newNPlayers; +} /* nplayers_menu_select */ + +static void +size_menu_select( GtkWidget* item, GtkNewGameState* state ) +{ + ItemNumPair pair; + + pair.item = item; + pair.index = 0; + pair.found = FALSE; + + gtk_container_foreach( GTK_CONTAINER(item->parent), countBeforeSame, &pair ); + + XP_DEBUGF( "changing nCols from %d to %d\n", state->nCols, + MAX_COLS - pair.index ); + state->nCols = MAX_COLS - pair.index; +} /* size_menu_select */ + +typedef struct LoadPair { + LocalPlayer* info; + XP_U16 counter; + MPSLOT +} LoadPair; + +static void +loadCopyValues( GtkWidget* item, gpointer data ) +{ + LoadPair* lp = (LoadPair*)data; + LocalPlayer* player = lp->info; + char* entryText; + + switch( lp->counter ) { + case 0: /* labels */ + case 2: + break; + case 1: /* name field */ + entryText = gtk_entry_get_text( GTK_ENTRY(item) ); + player->name = copyString( MPPARM(lp->mpool) entryText ); + break; + case 3: /* passwd field */ + entryText = gtk_entry_get_text( GTK_ENTRY(item) ); + player->password = copyString( MPPARM(lp->mpool) entryText ); + break; + case 4: /* is Robot */ + player->isRobot = GTK_WIDGET_STATE( item ) == GTK_STATE_ACTIVE; + break; + case 5: /* is local */ + player->isLocal = GTK_WIDGET_STATE( item ) == GTK_STATE_ACTIVE; + break; + default: + XP_ASSERT( 0 ); + } + ++lp->counter; +} /* loadCopyValues */ + +static void +handle_ok( GtkWidget* widget, void* closure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + CurGameInfo* gi = &state->globals->cGlobals.params->gi; + short i; + LoadPair lp; + + MPASSIGN( lp.mpool, state->globals->cGlobals.params->util->mpool ); + + gi->nPlayers = state->nPlayers; + gi->boardSize = state->nCols; /* they're the same for now */ + + for ( i = 0; i < state->nPlayers; ++i ) { + LocalPlayer* player = &gi->players[i]; + GtkWidget* hbox = state->playerEntries[i]; + + lp.info = player; + lp.counter = 0; + + /* Read values out of the items in the hbox, which are, in order, the + name entry, passwd entry, isLocal box and isRobot box */ + gtk_container_foreach( GTK_CONTAINER(hbox), loadCopyValues, &lp ); + } + + state->cancelled = XP_FALSE; + gtk_main_quit(); +} + +static void +handle_cancel( GtkWidget* widget, void* closure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + state->cancelled = XP_TRUE; + gtk_main_quit(); +} + +static void +handle_revert( GtkWidget* widget, void* closure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + state->revert = TRUE; + gtk_main_quit(); +} /* handle_revert */ + +GtkWidget* +make_menu_item( gchar* name, GtkSignalFunc func, gpointer data ) +{ + GtkWidget* item; + + item = gtk_menu_item_new_with_label( name ); + gtk_signal_connect( GTK_OBJECT(item), "activate", func, data ); + gtk_widget_show( item ); + + return item; +} /* make_menu_item */ + +static GtkWidget* +makeButton( char* text, GtkSignalFunc func, gpointer data ) +{ + GtkWidget* button = gtk_button_new_with_label( text ); + gtk_signal_connect( GTK_OBJECT(button), "clicked", func, data ); + gtk_widget_show( button ); + + return button; +} /* makeButton */ + +static GtkWidget* +makeNewGameDialog( GtkNewGameState* state ) +{ + GtkWidget* dialog; + GtkWidget* vbox; + GtkWidget* hbox; + GtkWidget* item; + GtkWidget* nPlayersMenu; + GtkWidget* boardSizeMenu; + GtkWidget* opt; + CurGameInfo* gi; + short i; + + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + vbox = gtk_vbox_new( FALSE, 0 ); + + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Number of players"), + FALSE, TRUE, 0 ); + + opt = gtk_option_menu_new(); + nPlayersMenu = gtk_menu_new(); + + gi = &state->globals->cGlobals.params->gi; + state->nPlayers = gi->nPlayers; + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + char buf[2]; + snprintf( buf, 2, "%d", i+1 ); + item = make_menu_item( buf, GTK_SIGNAL_FUNC(nplayers_menu_select), + state ); + gtk_menu_append( GTK_MENU(nPlayersMenu), item ); + if ( i+1 == state->nPlayers ) { + gtk_menu_set_active( GTK_MENU(nPlayersMenu), i ); + } + } + gtk_option_menu_set_menu( GTK_OPTION_MENU(opt), nPlayersMenu ); + + gtk_widget_show( opt ); + gtk_box_pack_start( GTK_BOX(hbox), opt, FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + GtkWidget* label; + GtkWidget* nameField = gtk_entry_new(); + GtkWidget* passwdField = gtk_entry_new_with_max_length( 6 ); + GtkWidget* robotCheck = gtk_check_button_new_with_label( "robot" ); + GtkWidget* localCheck = gtk_check_button_new_with_label( "is local" ); + hbox = gtk_hbox_new( FALSE, 0 ); + state->playerEntries[i] = hbox; + + label = gtk_label_new("name:"); + gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 0 ); + gtk_widget_show( label ); + + gtk_box_pack_start( GTK_BOX(hbox), nameField, FALSE, TRUE, 0 ); + gtk_widget_show( nameField ); + + label = gtk_label_new("passwd:"); + gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 0 ); + gtk_widget_show( label ); + + gtk_box_pack_start( GTK_BOX(hbox), passwdField, FALSE, TRUE, 0 ); + gtk_widget_show( passwdField ); + + gtk_box_pack_start( GTK_BOX(hbox), robotCheck, FALSE, TRUE, 0 ); + gtk_widget_show( robotCheck ); + + gtk_box_pack_start( GTK_BOX(hbox), localCheck, FALSE, TRUE, 0 ); + gtk_widget_show( localCheck ); + + if ( i < state->nPlayers ) { + XP_Bool isSet; + gtk_entry_set_text( + GTK_ENTRY(nameField), + gi->players[i].name ); + + isSet = gi->players[i].isRobot; + gtk_toggle_button_set_state( GTK_TOGGLE_BUTTON(robotCheck), + isSet ); + XP_DEBUGF( "isRobot set to %d\n", isSet ); + isSet = gi->players[i].isLocal; + gtk_toggle_button_set_state( GTK_TOGGLE_BUTTON(localCheck), + isSet ); + XP_DEBUGF( "isLocal set to %d\n", isSet ); + } else { + char buf[10]; + snprintf( buf, sizeof(buf), "Player %d", i+1 ); + gtk_entry_set_text( GTK_ENTRY(nameField), buf ); + + gtk_widget_set_sensitive( hbox, FALSE ); + gtk_toggle_button_set_state( GTK_TOGGLE_BUTTON(localCheck), + XP_TRUE ); + } + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + } + + /* board size choices */ + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Board size"), + FALSE, TRUE, 0 ); + + opt = gtk_option_menu_new(); + boardSizeMenu = gtk_menu_new(); + + state->nCols = gi->boardSize; + for ( i = 0; i < MAX_SIZE_CHOICES; ++i ) { + char buf[10]; + XP_U16 siz = MAX_COLS - i; + snprintf( buf, sizeof(buf), "%dx%d", siz, siz ); + item = make_menu_item( buf, GTK_SIGNAL_FUNC(size_menu_select), + state ); + gtk_menu_append( GTK_MENU(boardSizeMenu), item ); + if ( siz == state->nCols ) { + gtk_menu_set_active( GTK_MENU(boardSizeMenu), i ); + } + } + gtk_option_menu_set_menu( GTK_OPTION_MENU(opt), boardSizeMenu ); + + gtk_widget_show( opt ); + gtk_box_pack_start( GTK_BOX(hbox), opt, FALSE, TRUE, 0 ); + + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), + FALSE, TRUE, 0 ); + + XP_ASSERT( gi->dictName ); + gtk_box_pack_start( GTK_BOX(hbox), + gtk_label_new(gi->dictName), + FALSE, TRUE, 0 ); + + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + /* buttons at the bottom */ + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), + makeButton( "Ok", handle_ok, state ), + FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), + makeButton( "Revert", handle_revert, state ), + FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), + makeButton( "Cancel", handle_cancel, state ), + FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); + + gtk_widget_show_all (dialog); + + return dialog; +} /* makeNewGameDialog */ + +gboolean +newGameDialog( GtkAppGlobals* globals/* , GtkGameInfo* gameInfo */ ) +{ + GtkNewGameState state; + state.globals = globals; + + /* returns when button handler calls gtk_main_quit */ + do { + GtkWidget* dialog = makeNewGameDialog( &state ); + state.revert = FALSE; + gtk_main(); + gtk_widget_destroy( dialog ); + } while ( state.revert ); + + return !state.cancelled; +} /* newGameDialog */ + +#if 0 +gint +gtkask( GtkAppGlobals* globals, gchar *message, gint numButtons, + char* button1, ... ) +{ + GtkWidget* dialog; + GtkWidget* label; + GtkWidget* button; + short i; + gboolean* results = g_malloc( numButtons * sizeof(results[0]) ); + char** butList = &button1; + + /* Create the widgets */ + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + label = gtk_label_new( message ); + + for ( i = 0; i < numButtons; ++i ) { + button = gtk_button_new_with_label( *butList ); + + results[i] = 0; + gtk_signal_connect( GTK_OBJECT( button ), "clicked", + GTK_SIGNAL_FUNC(button_event), &results[i] ); + + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), + button ); + + ++butList; + } + + /* Add the label, and show everything we've added to the dialog. */ + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label); + gtk_widget_show_all (dialog); + + /* returns when button handler calls gtk_main_quit */ + gtk_main(); + + gtk_widget_destroy( dialog ); + + for ( i = 0; i < numButtons; ++i ) { + if ( results[i] ) { + break; + } + } + g_free( results ); + return i; + } /* gtkask */ + +#endif + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h new file mode 100644 index 000000000..a9df979bd --- /dev/null +++ b/xwords4/linux/gtknewgame.h @@ -0,0 +1,40 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + + +#ifdef PLATFORM_GTK + +#ifndef _GTKNEWGAME_H_ +#define _GTKNEWGAME_H_ + +#include "gtkmain.h" + +typedef struct GtkNewGameState { + GtkAppGlobals* globals; + GtkWidget* playerEntries[MAX_NUM_PLAYERS]; + gboolean revert; + gboolean cancelled; + short nPlayers; + short nCols; +} GtkNewGameState; + +gboolean newGameDialog( GtkAppGlobals* globals/* , GtkGameInfo* gameInfo */ ); + +#endif /* _GTKNEWGAME_H_ */ +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkpasswdask.c b/xwords4/linux/gtkpasswdask.c new file mode 100644 index 000000000..89e939fbf --- /dev/null +++ b/xwords4/linux/gtkpasswdask.c @@ -0,0 +1,94 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ +#ifdef PLATFORM_GTK + +#include "gtkpasswdask.h" +#include + +static void +button_event( GtkWidget* widget, void* closure ) +{ + XP_Bool* okptr = (XP_Bool*)closure; + *okptr = XP_TRUE; + gtk_main_quit(); +} /* ok_button_event */ + +XP_Bool +gtkpasswdask( const char* name, char* outbuf, XP_U16* buflen ) +{ + XP_Bool ok = XP_FALSE; + XP_Bool ignore; + char buf[64]; + short i; + + GtkWidget* entry; + GtkWidget* vbox; + GtkWidget* hbox; + GtkWidget* dialog; + GtkWidget* label; + + char* labels[] = { "Ok", "Cancel" }; + XP_Bool* boolps[] = { &ok, &ignore }; + + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + snprintf( buf, sizeof(buf), "Password for player \"%s\"", name ); + label = gtk_label_new( buf ); + + gtk_container_add( GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label ); + + /* we need a text field and two buttons as well */ + vbox = gtk_vbox_new(FALSE, 0); + + entry = gtk_entry_new(); + gtk_widget_show( entry ); + gtk_box_pack_start( GTK_BOX(vbox), entry, FALSE, TRUE, 0 ); + + hbox = gtk_hbox_new(FALSE, 0); + + for ( i = 0; i < 2; ++i ) { + GtkWidget* button = gtk_button_new_with_label( labels[i] ); + gtk_signal_connect( GTK_OBJECT(button), "clicked", button_event, + boolps[i] ); + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0 ); + gtk_widget_show( button ); + } + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); + + gtk_widget_show_all( dialog ); + + gtk_main(); + + if ( ok ) { + char* text = gtk_entry_get_text( GTK_ENTRY(entry) ); + strncpy( outbuf, text, *buflen ); + *buflen = strlen(outbuf); + } + + gtk_widget_destroy( dialog ); + + return ok; +} /* gtkpasswdask */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkpasswdask.h b/xwords4/linux/gtkpasswdask.h new file mode 100644 index 000000000..f491ea547 --- /dev/null +++ b/xwords4/linux/gtkpasswdask.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifdef PLATFORM_GTK + +#ifndef _GTKPASSWDASK_H_ +#define _GTKPASSWDASK_H_ + +#include "comtypes.h" + +XP_Bool gtkpasswdask( const char* name, char* buf, XP_U16* len ); + +#endif /* _GTKPASSWDASK_H_ */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/hint.xpm b/xwords4/linux/hint.xpm new file mode 100644 index 000000000..b9be7a036 --- /dev/null +++ b/xwords4/linux/hint.xpm @@ -0,0 +1,17 @@ +/* XPM */ +static char * hint_xpm[] = { +"8 11 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +".++++++.", +"+++..+++", +"++.++.++", +"+.++++.+", +"+.++++.+", +"++.++.++", +"++.++.++", +"+++..+++", +"+++..+++", +"+++..+++", +".++++++."}; diff --git a/xwords4/linux/juggle.xpm b/xwords4/linux/juggle.xpm new file mode 100644 index 000000000..edc11e718 --- /dev/null +++ b/xwords4/linux/juggle.xpm @@ -0,0 +1,14 @@ +/* XPM */ +static char * juggle_xpm[] = { +"8 8 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"........", +"...+++..", +"....+...", +"....+...", +"....+...", +"..+.+...", +"...+....", +"........"}; diff --git a/xwords4/linux/linuxdict.c b/xwords4/linux/linuxdict.c new file mode 100644 index 000000000..118ef8da8 --- /dev/null +++ b/xwords4/linux/linuxdict.c @@ -0,0 +1,372 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2002 by Eric House (fixin@peak.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. + */ + +#ifndef CLIENT_ONLY /* there's an else in the middle!!! */ + +#include +#include +/* #include */ + +#include "comtypes.h" +#include "dictnryp.h" +#include "linuxmain.h" + +typedef struct DictStart { + XP_U32 numNodes; + /* XP_U32 indexStart; */ + array_edge* array; +} DictStart; + +typedef struct LinuxDictionaryCtxt { + DictionaryCtxt super; + /* prc_t* pt; */ + /* DictStart* starts; */ + /* XP_U16 numStarts; */ +} LinuxDictionaryCtxt; + + +/************************ Prototypes ***********************/ +static XP_Bool initFromDictFile( LinuxDictionaryCtxt* dctx, char* fileName ); +static void linux_dictionary_destroy( DictionaryCtxt* dict ); + +/***************************************************************************** + * + ****************************************************************************/ +DictionaryCtxt* +linux_dictionary_make( MPFORMAL char* dictFileName ) +{ + LinuxDictionaryCtxt* result = + (LinuxDictionaryCtxt*)XP_MALLOC(mpool, sizeof(*result)); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->super.mpool, mpool); + + if ( !!dictFileName ) { + XP_Bool success = initFromDictFile( result, dictFileName ); + if ( success ) { + result->super.destructor = linux_dictionary_destroy; + setBlankTile( &result->super ); + } else { + XP_FREE( mpool, result ); + result = NULL; + } + } + + return (DictionaryCtxt*)result; +} /* gtk_dictionary_make */ + +static XP_U16 +countSpecials( LinuxDictionaryCtxt* ctxt ) +{ + XP_U16 result = 0; + XP_U16 i; + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i] ) ) { + ++result; + } + } + + return result; +} /* countSpecials */ + +static XP_Bitmap +skipBitmap( LinuxDictionaryCtxt* ctxt, FILE* dictF ) +{ + XP_U8 nCols, nRows, nBytes; + LinuxBMStruct* lbs = NULL; + + (void)fread( &nCols, sizeof(nCols), 1, dictF ); + if ( nCols > 0 ) { + (void)fread( &nRows, sizeof(nRows), 1, dictF ); + + nBytes = ((nRows * nCols) + 7) / 8; + + lbs = XP_MALLOC( ctxt->super.mpool, sizeof(*lbs) + nBytes ); + lbs->nRows = nRows; + lbs->nCols = nCols; + lbs->nBytes = nBytes; + + (void)fread( lbs + 1, nBytes, 1, dictF ); + } + + return lbs; +} /* skipBitmap */ + +static void +skipBitmaps( LinuxDictionaryCtxt* ctxt, FILE* dictF ) +{ + XP_U16 nSpecials; + XP_UCHAR* text; + XP_UCHAR** texts; + SpecialBitmaps* bitmaps; + Tile tile; + + nSpecials = countSpecials( ctxt ); + + texts = (XP_UCHAR**)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*texts) ); + bitmaps = (SpecialBitmaps*)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*bitmaps) ); + + for ( tile = 0; tile < ctxt->super.nFaces; ++tile ) { + + XP_CHAR16 face = ctxt->super.faces16[(short)tile]; + if ( IS_SPECIAL(face) ) { + XP_U8 txtlen; + XP_ASSERT( face < nSpecials ); + + /* get the string */ + (void)fread( &txtlen, sizeof(txtlen), 1, dictF ); + text = (XP_UCHAR*)XP_MALLOC(ctxt->super.mpool, txtlen+1); + (void)fread( text, txtlen, 1, dictF ); + text[txtlen] = '\0'; + texts[face] = text; + + XP_DEBUGF( "skipping bitmaps for %s", texts[face] ); + + bitmaps[face].largeBM = skipBitmap( ctxt, dictF ); + bitmaps[face].smallBM = skipBitmap( ctxt, dictF ); + } + } + + ctxt->super.chars = texts; + ctxt->super.bitmaps = bitmaps; +} /* skipBitmaps */ + +static XP_Bool +initFromDictFile( LinuxDictionaryCtxt* dctx, char* fileName ) +{ + XP_Bool formatOk = XP_TRUE; + XP_U8 numFaces; + long curPos, dictLength; + XP_U32 topOffset; + FILE* dictF = fopen( fileName, "r" ); + unsigned short xloc; + XP_U16 flags; + XP_U16 facesSize; + + XP_ASSERT( dictF ); + (void)fread( &flags, sizeof(flags), 1, dictF ); + flags = ntohs(flags); + XP_DEBUGF( "flags=0x%x", flags ); +#ifdef NODE_CAN_4 + if ( flags == 0x0001 ) { + dctx->super.nodeSize = 3; + dctx->super.charSize = 1; + } else if ( flags == 0x0002 ) { + dctx->super.nodeSize = 3; + dctx->super.charSize = 2; + } else if ( flags == 0x0003 ) { + dctx->super.nodeSize = 4; + dctx->super.charSize = 2; + } else { + /* case I don't know how to deal with */ + formatOk = XP_FALSE; + XP_ASSERT(0); + } +#else + XP_ASSERT( flags == 0x0001 ); +#endif + + if ( formatOk ) { + (void)fread( &numFaces, sizeof(numFaces), 1, dictF ); + + dctx->super.nFaces = numFaces; + + dctx->super.countsAndValues = XP_MALLOC( dctx->super.mpool, + numFaces*2 ); + facesSize = numFaces * sizeof(dctx->super.faces16[0]); + dctx->super.faces16 = XP_MALLOC( dctx->super.mpool, facesSize ); + XP_MEMSET( dctx->super.faces16, 0, facesSize ); + + fread( dctx->super.faces16, numFaces * dctx->super.charSize, + 1, dictF ); + if ( dctx->super.charSize == sizeof(dctx->super.faces16[0]) ) { + /* fix endianness */ + XP_U16 i; + for ( i = 0; i < numFaces; ++i ) { + XP_CHAR16 tmp = dctx->super.faces16[i]; + dctx->super.faces16[i] = ntohs(tmp); + } + } else { + XP_UCHAR* src = ((XP_UCHAR*)(dctx->super.faces16)) + numFaces; + XP_CHAR16* dest = dctx->super.faces16 + numFaces; + while ( src-- <= (XP_UCHAR*)(dest--) ) { + *dest = (XP_CHAR16)*src; + } + } + + fread( &xloc, 2, 1, dictF ); /* read in (dump) the xloc header for + now */ + fread( dctx->super.countsAndValues, numFaces*2, 1, dictF ); + + skipBitmaps( dctx, dictF ); + + curPos = ftell( dictF ); + fseek( dictF, 0L, SEEK_END ); + dictLength = ftell( dictF ) - curPos; + fseek( dictF, curPos, SEEK_SET ); + + if ( dictLength > 0 ) { + fread( &topOffset, sizeof(topOffset), 1, dictF ); + /* it's in big-endian order */ + topOffset = ntohl(topOffset); + dictLength -= sizeof(topOffset); /* first four bytes are offset */ + } + + if ( dictLength > 0 ) { +#ifdef DEBUG + dctx->super.numEdges = dictLength / 3; +#endif + +#ifdef NODE_CAN_4 + XP_ASSERT( (dictLength % dctx->super.nodeSize) == 0 ); +#else + XP_ASSERT( (dictLength % 3) == 0 ); +#endif + + dctx->super.base = (array_edge*)XP_MALLOC( dctx->super.mpool, + dictLength ); + XP_ASSERT( !!dctx->super.base ); + fread( dctx->super.base, dictLength, 1, dictF ); + + dctx->super.topEdge = dctx->super.base + topOffset; + } else { + dctx->super.base = NULL; + dctx->super.topEdge = NULL; + } + } + + fclose( dictF ); + return formatOk; +} /* initFromDictFile */ + +static void +freeSpecials( LinuxDictionaryCtxt* ctxt ) +{ + XP_U16 nSpecials = 0; + XP_U16 i; + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i]) ) { + if ( !!ctxt->super.bitmaps ) { + XP_Bitmap* bmp = ctxt->super.bitmaps[nSpecials].largeBM; + if ( !!bmp ) { + XP_FREE( ctxt->super.mpool, bmp ); + } + bmp = ctxt->super.bitmaps[nSpecials].smallBM; + if ( !!bmp ) { + XP_FREE( ctxt->super.mpool, bmp ); + } + } + if ( !!ctxt->super.chars && !!ctxt->super.chars[nSpecials]) { + XP_FREE( ctxt->super.mpool, ctxt->super.chars[nSpecials] ); + } + ++nSpecials; + } + } + if ( !!ctxt->super.bitmaps ) { + XP_FREE( ctxt->super.mpool, ctxt->super.bitmaps ); + } + if ( !!ctxt->super.chars ) { + XP_FREE( ctxt->super.mpool, ctxt->super.chars ); + } +} /* freeSpecials */ + +static void +linux_dictionary_destroy( DictionaryCtxt* dict ) +{ + LinuxDictionaryCtxt* ctxt = (LinuxDictionaryCtxt*)dict; + + freeSpecials( ctxt ); + + if ( !!dict->topEdge ) { + XP_FREE( dict->mpool, dict->topEdge ); + } + + XP_FREE( dict->mpool, ctxt->super.countsAndValues ); + XP_FREE( dict->mpool, ctxt->super.faces16 ); + XP_FREE( dict->mpool, ctxt ); +} /* linux_dictionary_destroy */ + +#else /* CLIENT_ONLY *IS* defined */ + +/* initFromDictFile: + * This guy reads in from a prc file, and probably hasn't worked in a year. + */ +#define RECS_BEFORE_DAWG 3 /* a hack */ +static XP_Bool +initFromDictFile( LinuxDictionaryCtxt* dctx, char* fileName ) +{ + short i; + unsigned short* dataP; + unsigned nRecs; + prc_record_t* prect; + + prc_t* pt = prcopen( fileName, PRC_OPEN_READ ); + dctx->pt = pt; /* remember so we can close it later */ + + nRecs = prcgetnrecords( pt ); + + /* record 0 holds a struct whose 5th byte is the record num of the first + dawg record. 1 and 2 hold tile data. Let's assume 3 is the first dawg + record for now. */ + + prect = prcgetrecord( pt, 1 ); + dctx->super.numFaces = prect->datalen; /* one char per byte */ + dctx->super.faces = malloc( prect->datalen ); + memcpy( dctx->super.faces, prect->data, prect->datalen ); + + dctx->super.counts = malloc( dctx->super.numFaces ); + dctx->super.values = malloc( dctx->super.numFaces ); + + prect = prcgetrecord( pt, 2 ); + dataP = (unsigned short*)prect->data + 1; /* skip the xloc header */ + + for ( i = 0; i < dctx->super.numFaces; ++i ) { + unsigned short byt = *dataP++; + dctx->super.values[i] = byt >> 8; + dctx->super.counts[i] = byt & 0xFF; + if ( dctx->super.values[i] == 0 ) { + dctx->super.counts[i] = 4; /* 4 blanks :-) */ + } + } + + dctx->numStarts = nRecs - RECS_BEFORE_DAWG; + dctx->starts = XP_MALLOC( dctx->numStarts * sizeof(*dctx->starts) ); + + for ( i = 0/* , offset = 0 */; i < dctx->numStarts; ++i ) { + prect = prcgetrecord( pt, i + RECS_BEFORE_DAWG ); + dctx->starts[i].numNodes = prect->datalen / 3; + dctx->starts[i].array = (array_edge*)prect->data; + + XP_ASSERT( (prect->datalen % 3) == 0 ); + } +} /* initFromDictFile */ + +void +linux_dictionary_destroy( DictionaryCtxt* dict ) +{ + LinuxDictionaryCtxt* ctxt = (LinuxDictionaryCtxt*)dict; + prcclose( ctxt->pt ); +} + +#endif /* CLIENT_ONLY */ + diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c new file mode 100644 index 000000000..8b3f39c5a --- /dev/null +++ b/xwords4/linux/linuxmain.c @@ -0,0 +1,762 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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 +#include +#include + +#include /* gethostbyname */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linuxmain.h" +#include "main.h" +#ifdef PLATFORM_NCURSES +# include "cursesmain.h" +#endif +#ifdef PLATFORM_GTK +# include "gtkmain.h" +#endif +#include "model.h" +#include "util.h" +#include "strutils.h" +/* #include "commgr.h" */ +/* #include "compipe.h" */ +#include "memstream.h" +#include "LocalizedStrIncludes.h" + +#define DEFAULT_SEND_PORT 4999 +#define DEFAULT_LISTEN_PORT 4998 + +#ifdef DEBUG +void +linux_debugf( char* format, ... ) +{ + char buf[1000]; + va_list ap; + // time_t tim; + struct tm* timp; + struct timeval tv; + struct timezone tz; + + gettimeofday( &tv, &tz ); + timp = localtime( &tv.tv_sec ); + + sprintf( buf, "%d:%d:%d: ", timp->tm_hour, timp->tm_min, timp->tm_sec ); + + va_start(ap, format); + + vsprintf(buf+strlen(buf), format, ap); + + va_end(ap); + + fprintf( stderr, buf ); + fprintf( stderr, "\n" ); +} +#endif + +void +catOnClose( XWStreamCtxt* stream, void* closure ) +{ + XP_U16 nBytes; + char* buffer; + + XP_LOGF( "catOnClose" ); + + nBytes = stream_getSize( stream ); + buffer = malloc( nBytes + 1 ); + stream_getBytes( stream, buffer, nBytes ); + buffer[nBytes] = '\0'; + + fprintf( stderr, buffer ); + + free( buffer ); +} /* catOnClose */ + +void +catGameHistory( CommonGlobals* cGlobals ) +{ + if ( !!cGlobals->game.model ) { + XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server ); + XWStreamCtxt* stream = + mem_stream_make( MPPARM(cGlobals->params->util->mpool) + cGlobals->params->vtMgr, + NULL, CHANNEL_NONE, catOnClose ); + model_writeGameHistory( cGlobals->game.model, stream, + cGlobals->game.server, gameOver ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); + } +} /* catGameHistory */ + +XP_UCHAR* +strFromStream( XWStreamCtxt* stream ) +{ + XP_U16 len = stream_getSize( stream ); + XP_UCHAR* buf = (XP_UCHAR*)malloc( len + 1 ); + stream_getBytes( stream, buf, len ); + buf[len] = '\0'; + + return buf; +} /* strFromStream */ + +XP_UCHAR* +linux_getErrString( UtilErrID id ) +{ + char* message = NULL; + + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + message = "All tiles played must be in a line."; + break; + case ERR_NO_EMPTIES_IN_TURN: + message = "Empty squares cannot separate tiles played."; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + message = "Must play two or more pieces on the first move."; + break; + case ERR_TILES_MUST_CONTACT: + message = "New pieces must contact others already in place (or " + "the middle square on the first move)."; + break; + case ERR_NOT_YOUR_TURN: + message = "You can't do that; it's not your turn!"; + break; + case ERR_NO_PEEK_ROBOT_TILES: + message = "No peeking at the robot's tiles!"; + break; + case ERR_NO_PEEK_REMOTE_TILES: + message = "No peeking at remote players' tiles!"; + break; + case ERR_REG_UNEXPECTED_USER: + message = "Refused attempt to register unexpected user[s]."; + break; + case ERR_SERVER_DICT_WINS: + message = "Conflict between Host and Guest dictionaries; Host wins."; + XP_WARNF( "GTK may have problems here." ); + break; + default: + message = ""; + } + + return message; +} /* linux_getErrString */ + +static void +usage( char* appName, char* msg ) +{ + if ( msg != NULL ) { + fprintf( stderr, "Error: %s\n\n", msg ); + } + fprintf( stderr, "usage: %s \n" +#if defined PLATFORM_GTK && defined PLATFORM_NCURSES + "\t [-g] # gtk (default)\n" + "\t [-u] # ncurses (for dumb terminal)\n" +#endif +#if defined PLATFORM_GTK + "\t [-o] # tray overlaps board (like small screen)\n" + "\t [-k] # ask for parameters via \"new games\" dlg\n" +#endif + "\t [-f file] # use this file to save/load game\n" + "\t [-q] # quit when game over (useful for robot-only)\n" + "\t [-S] # slow robot down \n" + "\t [-i] # print game history when game over\n" + "\t [-U] # call 'Undo' after game ends\n" + "\t [-r name]* # same-process robot\n" + "\t [-n name]* # same-process player (no network used)\n" + "\t [-w pwd]* # passwd for matching local player\n" + "\t [-v] # put scoreboard in vertical mode\n" + "\t [-m] # make the robot duMb (smart is default)\n" + "\t [-c] # explain robot scores after each move\n" + "\t\t # (max of four players total, local and remote)\n" + "\t [-b boardSize] # number of columns and rows\n" + "\t [-l listen_port] # port for inet connection (4999 default)\n" + "\t [-e random_seed] \n" + "\t [-t initial_minutes_on_timer] \n" + "# --------------- choose client or server ----------\n" + "\t -s # be the server\n" + "\t -d xwd_file # provides tile counts & values\n" + "\t\t # list each player as local or remote\n" + "\t [-N]* # remote client (listen for connection)\n" + "# --------------- OR client-only ----------\n" + "\t [-a server_addr] # be a client (on port spec'd above)\n" + "\t [-p client_port] # must != server's port if on same device" + "" " (default localhost)\n" + "\nexample: \n" + "\tserver: ./xwords -d dict.xwd -s -r Eric -N\n" + "\tclient: ./xwords -d dict.xwd -r Kati -p 4999 -l 6000\n" + , appName ); + exit(1); +} + +XP_S16 +linux_udp_send( XP_U8* buf, XP_U16 buflen, CommsAddrRec* addrRec, + void* closure ) +{ + CommonGlobals* globals = (CommonGlobals*)closure; + struct sockaddr_in to_sock; + struct hostent* host; + XP_S16 result; + int sock; +/* XP_U8* msg; */ + + XP_LOGF( "linux_udp_send" ); + + /* make a local copy of the address to send to */ + sock = socket( AF_INET, SOCK_DGRAM, 0 ); + if ( sock == -1 ) { + XP_DEBUGF( "socket returned -1\n" ); + return -1; + } + + if ( !!addrRec ) { + + XP_ASSERT( addrRec->conType == COMMS_CONN_IP ); + XP_MEMSET( &to_sock, 0, sizeof(to_sock) ); + XP_STATUSF( "target IP = 0x%lx", addrRec->u.ip.ipAddr ); + to_sock.sin_addr.s_addr = htonl(addrRec->u.ip.ipAddr); + to_sock.sin_port = htons(addrRec->u.ip.port); + + XP_STATUSF( "0: sending to port %d(0x%x)", + addrRec->u.ip.port, addrRec->u.ip.port ); + } else { + + to_sock.sin_port = htons(globals->params->defaultSendPort); + XP_STATUSF( "1: sending to port %d", + globals->params->defaultSendPort ); + if (( host = gethostbyname(globals->defaultServerName) ) == NULL ) { + XP_WARNF( "gethostbyname returned -1\n" ); + return -1; + } else { + XP_WARNF( "gethostbyname for %s worked", + globals->defaultServerName ); + } + memcpy( &(to_sock.sin_addr.s_addr), host->h_addr_list[0], + sizeof(struct in_addr)); + } + to_sock.sin_family = AF_INET; + +/* msg = malloc( buflen ); */ +/* XP_MEMCPY( msg, buf, buflen ); */ + + errno = 0; + XP_STATUSF( "checking: errno=%d", (short)errno ); + result = sendto( sock, buf, buflen, 0, + (struct sockaddr *)&to_sock, sizeof(to_sock) ); + XP_STATUSF( "foo: sendto returned %d of %d (err=%d)", + result, buflen, errno ); + close(sock); + +/* free( msg ); */ + + return result; +} /* linux_udp_send */ + +int +initListenerSocket( int port ) +{ + int newSocket; + int result; + struct sockaddr_in sockAddr; + + XP_DEBUGF( "opening socket to listen on port %d", port ); + + newSocket = socket( AF_INET, DGRAM_TYPE, 0 ); + XP_DEBUGF( "socket returned %d", newSocket ); + + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); + sockAddr.sin_port = htons(port); + + result = bind( newSocket, (struct sockaddr*)&sockAddr, sizeof(sockAddr) ); + XP_LOGF( "bind on port %d returned %d; errno=%d", + port, result, errno ); + + +/* result = listen( newSocket, 5 ); */ +/* XP_DEBUGF( "listen returned %d; errno=%d\n", result, errno ); */ + + return newSocket; +} /* initListenerSocket */ + +/* Create a stream for the incomming message buffer, and read in any + information specific to our platform's comms layer (return address, say) + */ +XWStreamCtxt* +stream_from_msgbuf( CommonGlobals* globals, char* bufPtr, XP_U16 nBytes ) +{ + XWStreamCtxt* result; + result = mem_stream_make( MPPARM(globals->params->util->mpool) + globals->params->vtMgr, + globals, CHANNEL_NONE, NULL ); + stream_putBytes( result, bufPtr, nBytes ); + + return result; +} /* stream_from_msgbuf */ + +#if 0 +static void +streamClosed( XWStreamCtxt* stream, XP_PlayerAddr addr, void* closure ) +{ + fprintf( stderr, "streamClosed called\n" ); +} /* streamClosed */ + +static XWStreamCtxt* +linux_util_makeStreamFromAddr( XW_UtilCtxt* uctx, XP_U16 channelNo ) +{ +#if 1 +/* XWStreamCtxt* stream = linux_mem_stream_make( uctx->closure, channelNo, */ +/* sendOnClose, NULL ); */ +#else + struct sockaddr* returnAddr = (struct sockaddr*)addr; + int newSocket; + int result; + + newSocket = socket( AF_INET, DGRAM_TYPE, 0 ); + fprintf( stderr, "linux_util_makeStreamFromAddr: made socket %d\n", + newSocket ); + /* #define EADDRINUSE 98 */ + result = bind( newSocket, (struct sockaddr*)returnAddr, addrLen ); + fprintf( stderr, "bind returned %d; errno=%d\n", result, errno ); + + return linux_make_socketStream( newSocket ); +#endif +} /* linux_util_makeStreamFromAddr */ +#endif + +static DictionaryCtxt* +linux_util_makeEmptyDict( XW_UtilCtxt* uctx ) +{ + XP_DEBUGF( "linux_util_makeEmptyDict called\n" ); + return linux_dictionary_make( MPPARM(uctx->mpool) NULL ); +} /* linux_util_makeEmptyDict */ + +#define EM BONUS_NONE +#define DL BONUS_DOUBLE_LETTER +#define DW BONUS_DOUBLE_WORD +#define TL BONUS_TRIPLE_LETTER +#define TW BONUS_TRIPLE_WORD + +static XWBonusType +linux_util_getSquareBonus( XW_UtilCtxt* uc, ModelCtxt* model, + XP_U16 col, XP_U16 row ) +{ + XP_U16 index; + /* This must be static or won't compile under multilink (for Palm). + Fix! */ + static char scrabbleBoard[8*8] = { + TW,EM,EM,DL,EM,EM,EM,TW, + EM,DW,EM,EM,EM,TL,EM,EM, + + EM,EM,DW,EM,EM,EM,DL,EM, + DL,EM,EM,DW,EM,EM,EM,DL, + + EM,EM,EM,EM,DW,EM,EM,EM, + EM,TL,EM,EM,EM,TL,EM,EM, + + EM,EM,DL,EM,EM,EM,DL,EM, + TW,EM,EM,DL,EM,EM,EM,DW, + }; /* scrabbleBoard */ + + if ( col > 7 ) col = 14 - col; + if ( row > 7 ) row = 14 - row; + index = (row*8) + col; + if ( index >= 8*8 ) { + return (XWBonusType)EM; + } else { + return (XWBonusType)scrabbleBoard[index]; + } +} /* linux_util_getSquareBonus */ + +static XP_U32 +linux_util_getCurSeconds( XW_UtilCtxt* uc ) +{ + return (XP_U32)time(NULL);//tv.tv_sec; +} /* gtk_util_getCurSeconds */ + +static XP_UCHAR* +linux_util_getUserString( XW_UtilCtxt* uc, XP_U16 code ) +{ + switch( code ) { + case STRD_REMAINING_TILES_ADD: + return "+ %d [all remaining tiles]"; + case STRD_UNUSED_TILES_SUB: + return "- %d [unused tiles]"; + case STR_COMMIT_CONFIRM: + return "Are you sure you want to commit the current move?\n"; + case STRD_TURN_SCORE: + return "Score for turn: %d\n"; + case STR_BONUS_ALL: + return "Bonus for using all tiles: 50\n"; + case STR_NONLOCAL_NAME: + return "%s (remote)"; + case STRD_TIME_PENALTY_SUB: + return " - %d [time]"; + /* added.... */ + case STRD_CUMULATIVE_SCORE: + return "Cumulative score: %d\n"; + case STRS_TRAY_AT_START: + return "Tray at start: %s\n"; + case STRS_MOVE_DOWN: + return "move (from %s down)\n"; + case STRS_MOVE_ACROSS: + return "move (from %s across)\n"; + case STRS_NEW_TILES: + return "New tiles: %s\n"; + case STRSS_TRADED_FOR: + return "Traded %s for %s."; + case STR_PASS: + return "pass\n"; + case STR_PHONY_REJECTED: + return "Illegal word in move; turn lost!\n"; + + case STRD_ROBOT_TRADED: + return "%d tiles traded this turn."; + case STR_ROBOT_MOVED: + return "The robot moved:\n"; + case STR_REMOTE_MOVED: + return "Remote player moved:\n"; + + default: + return "unknown code to linux_util_getUserString"; + } +} /* linux_util_getUserString */ + +static unsigned int +defaultRandomSeed() +{ + /* use kernel device rather than time() so can run multiple times/second + without getting the same results. */ + unsigned int rs; + FILE* rfile = fopen( "/dev/urandom", "ro" ); + fread( &rs, sizeof(rs), 1, rfile ); + fclose( rfile ); + return rs; +} /* defaultRandomSeed */ + +int +main( int argc, char** argv ) +{ + XP_Bool useGTK, useCurses; + int opt; + int totalPlayerCount = 0; + XP_Bool isServer = XP_FALSE; + char* listenPortNumString = NULL; + char* sendPortNumString = NULL; + char* serverName = "localhost"; + unsigned int seed = defaultRandomSeed(); + LaunchParams mainParams; + XP_U16 robotCount = 0; + +#ifdef DEBUG + { + int i; + for ( i = 0; i < argc; ++i ) { + XP_LOGF( argv[i] ); + } + } +#endif + + memset( &mainParams, 0, sizeof(mainParams) ); + + mainParams.util = malloc( sizeof(*mainParams.util) ); + XP_MEMSET( mainParams.util, 0, sizeof(*mainParams.util) ); + +#ifdef MEM_DEBUG + mainParams.util->mpool = mpool_make(); +#endif + + mainParams.vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mainParams.util->mpool)); + + /* fprintf( stdout, "press to start\n" ); */ + /* (void)fgetc( stdin ); */ + + /* defaults */ + mainParams.defaultListenPort = DEFAULT_LISTEN_PORT; + mainParams.defaultSendPort = DEFAULT_SEND_PORT; + mainParams.trayOverlaps = XP_FALSE; + mainParams.gi.boardSize = 15; + mainParams.quitAfter = XP_FALSE; + mainParams.sleepOnAnchor = XP_FALSE; + mainParams.printHistory = XP_FALSE; + mainParams.undoWhenDone = XP_FALSE; + mainParams.gi.timerEnabled = XP_FALSE; + mainParams.gi.robotSmartness = SMART_ROBOT; + /* serverName = mainParams.info.clientInfo.serverName = "localhost"; */ + +#if defined PLATFORM_GTK + useGTK = 1; + useCurses = 0; +#else /* curses is the default if GTK isn't available */ + useGTK = 0; + useCurses = 1; +#endif + + + + do { + short index; + opt = getopt( argc, argv, "?" +#if defined PLATFORM_GTK && defined PLATFORM_NCURSES + "gu" +#endif +#if defined PLATFORM_GTK + "o" +#endif + "kKf:l:n:Nsd:a:p:e:r:b:qw:Sit:Umvc" ); + switch( opt ) { + case 'h': + case '?': + usage(argv[0], NULL); + break; + case 'c': + mainParams.showRobotScores = XP_TRUE; + break; + case 'd': + mainParams.gi.dictName = copyString( MPPARM(mainParams.util->mpool) + optarg ); + break; + case 'e': + seed = atoi(optarg); + break; + case 'f': + mainParams.fileName = optarg; + break; + case 'i': + mainParams.printHistory = 1; + break; + case 'K': + mainParams.skipWarnings = 1; + break; + case 'w': + mainParams.gi.players[mainParams.nLocalPlayers-1].password + = optarg; + break; + case 'm': /* dumb robot */ + mainParams.gi.robotSmartness = DUMB_ROBOT; + break; + case 'n': + index = mainParams.gi.nPlayers++; + ++mainParams.nLocalPlayers; + mainParams.gi.players[index].isRobot = XP_FALSE; + mainParams.gi.players[index].isLocal = XP_TRUE; + mainParams.gi.players[index].name = + copyString(MPPARM(mainParams.util->mpool) optarg); + break; + case 'N': + index = mainParams.gi.nPlayers++; + mainParams.gi.players[index].isLocal = XP_FALSE; + ++mainParams.info.serverInfo.nRemotePlayers; + break; + case 'l': + listenPortNumString = optarg; + break; + case 'p': + sendPortNumString = optarg; + break; + case 'r': + ++robotCount; + index = mainParams.gi.nPlayers++; + ++mainParams.nLocalPlayers; + mainParams.gi.players[index].isRobot = XP_TRUE; + mainParams.gi.players[index].isLocal = XP_TRUE; + mainParams.gi.players[index].name = + copyString(MPPARM(mainParams.util->mpool) optarg); + break; + case 's': + isServer = XP_TRUE; + break; + case 'S': + mainParams.sleepOnAnchor = XP_TRUE; + break; + case 't': + mainParams.gi.gameSeconds = atoi(optarg) * 60; + mainParams.gi.timerEnabled = XP_TRUE; + break; + case 'U': + mainParams.undoWhenDone = XP_TRUE; + break; + case 'a': + /* mainParams.info.clientInfo.serverName = */ + serverName = optarg; + break; + case 'q': + mainParams.quitAfter = XP_TRUE; + break; + case 'b': + mainParams.gi.boardSize = atoi(optarg); + break; + case 'v': + mainParams.verticalScore = XP_TRUE; + break; +#if defined PLATFORM_GTK && defined PLATFORM_NCURSES + case 'g': + useGTK = 1; + break; + case 'u': + useCurses = 1; + useGTK = 0; + break; +#endif +#if defined PLATFORM_GTK + case 'o': + mainParams.trayOverlaps = XP_TRUE; + break; + case 'k': + mainParams.askNewGame = XP_TRUE; + break; +#endif + } + } while ( opt != -1 ); + + XP_ASSERT( mainParams.gi.nPlayers == mainParams.nLocalPlayers + + mainParams.info.serverInfo.nRemotePlayers ); + + if ( isServer ) { + if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { + mainParams.gi.serverRole = SERVER_STANDALONE; + } else { + mainParams.gi.serverRole = SERVER_ISSERVER; + } + } else { + mainParams.gi.serverRole = SERVER_ISCLIENT; + } + + /* convert strings to whatever */ + if ( listenPortNumString ) { + mainParams.defaultListenPort = atoi( listenPortNumString ); + } + if ( sendPortNumString != NULL ) { + mainParams.defaultSendPort = atoi( sendPortNumString ); + } + + /* sanity checks */ + totalPlayerCount = mainParams.nLocalPlayers + + mainParams.info.serverInfo.nRemotePlayers; + if ( !mainParams.fileName ) { + if ( (totalPlayerCount < 1) || + (totalPlayerCount > MAX_NUM_PLAYERS) ) { + usage( argv[0], "Need between 1 and 4 players" ); + } + } + + if ( !!mainParams.gi.dictName ) { + mainParams.dict = linux_dictionary_make( + MPPARM(mainParams.util->mpool) mainParams.gi.dictName ); + XP_ASSERT( !!mainParams.dict ); + } else if ( isServer ) { +#ifdef STUBBED_DICT + mainParams.dict = make_stubbed_dict( + MPPARM_NOCOMMA(mainParams.util->mpool) ); + XP_WARNF( "no dictionary provided: using English stub dict\n" ); +#else + usage( argv[0], "Server needs a dictionary" ); +#endif + } else if ( robotCount > 0 ) { + usage( argv[0], "Client can't have robots without a dictionary" ); + } + + if ( !isServer ) { + if ( mainParams.info.serverInfo.nRemotePlayers > 0 ) { + usage( argv[0], "Client can't have remote players" ); + } + } + + /* setup sockets and any other stuff not specific to GTK or ncurses */ + if ( isServer ) { + } else { + /* struct hostent* hostinfo; */ + /* hostinfo = gethostbyname( serverName ); */ + /* if ( !hostinfo ) { */ + /* fprintf( stderr, "unable to get host info for %s\n", serverName ); */ + /* exit( 0 ); */ + /* } else { */ + /* char* hostName = inet_ntoa( *(struct in_addr*)hostinfo->h_addr ); */ + /* unsigned long int serverAddr; */ + /* fprintf( stderr, "gethostbyname returned %s\n", hostName ); */ + /* serverAddr = inet_addr(hostName); */ + /* fprintf( stderr, "inet_addr returned %lu\n", serverAddr ); */ + + /* mainParams.info.clientInfo.stream = */ + /* linux_make_outbound_socketStream( serverAddr, serverPort ); */ + + /* if ( !stream_open( mainParams.info.clientInfo.stream ) { */ + /* fprintf( stderr, "ERROR: unable to connect to server\n" ); */ + /* exit(0); */ + /* } */ + /* } */ + mainParams.info.clientInfo.serverName = serverName; + } + + /* mainParams.pipe = linuxCommPipeCtxtMake( isServer ); */ + + mainParams.util->vtable = malloc( sizeof(UtilVtable) ); + /* mainParams.util->vtable->m_util_makeStreamFromAddr = */ + /* linux_util_makeStreamFromAddr; */ + + mainParams.util->gameInfo = &mainParams.gi; + + mainParams.util->vtable->m_util_makeEmptyDict = + linux_util_makeEmptyDict; + mainParams.util->vtable->m_util_getSquareBonus = + linux_util_getSquareBonus; + mainParams.util->vtable->m_util_getCurSeconds = + linux_util_getCurSeconds; + mainParams.util->vtable->m_util_getUserString = + linux_util_getUserString; + + srandom( seed ); /* init linux random number generator */ + XP_LOGF( "seeded srandom with %d", seed ); + + if ( isServer ) { + if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { + mainParams.serverRole = SERVER_STANDALONE; + } else { + mainParams.serverRole = SERVER_ISSERVER; + } + } else { + mainParams.serverRole = SERVER_ISCLIENT; + } + + if ( mainParams.nLocalPlayers > 0 || !!mainParams.fileName) { + if ( useCurses ) { +#if defined PLATFORM_NCURSES + cursesmain( isServer, &mainParams ); +#endif + } else { +#if defined PLATFORM_GTK + gtkmain( isServer, &mainParams, argc, argv ); +#endif + } + } else { + /* run server as faceless process? */ + } + + dict_destroy( mainParams.dict ); + + return 0; +} /* main */ + diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h new file mode 100644 index 000000000..477828872 --- /dev/null +++ b/xwords4/linux/linuxmain.h @@ -0,0 +1,50 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make -k";-*- */ +/* + * Copyright 1997-2000 by Eric House (fixin@peak.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. + */ +#ifndef _LINUXMAIN_H_ +#define _LINUXMAIN_H_ + +#include "main.h" +#include "dictnry.h" +#include "mempool.h" +#include "comms.h" +#include "memstream.h" +/* #include "compipe.h" */ + +extern int errno; + +typedef struct LinuxBMStruct { + XP_U8 nCols; + XP_U8 nRows; + XP_U8 nBytes; +} LinuxBMStruct; + +DictionaryCtxt* linux_dictionary_make( MPFORMAL char* dictFileName ); + +int initListenerSocket( int port ); +XP_S16 linux_udp_send( XP_U8* buf, XP_U16 buflen, CommsAddrRec* addrRec, + void* closure ); +XWStreamCtxt* stream_from_msgbuf( CommonGlobals* globals, char* bufPtr, + XP_U16 nBytes ); +XP_UCHAR* linux_getErrString( UtilErrID id ); +XP_UCHAR* strFromStream( XWStreamCtxt* stream ); + +void catGameHistory( CommonGlobals* cGlobals ); +void catOnClose( XWStreamCtxt* stream, void* closure ); + +#endif diff --git a/xwords4/linux/linuxserver.c b/xwords4/linux/linuxserver.c new file mode 100644 index 000000000..1b558d327 --- /dev/null +++ b/xwords4/linux/linuxserver.c @@ -0,0 +1,34 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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 "linuxserver.h" + + +ServerCtxt* +linux_make_server( char* dictName, XP_U16 totalPlayerCount ) +{ + return NULL; +} /* linux_make_server */ + +ServerCtxt* +linux_make_serverProxy( char* serverAddrStr, XP_U16 totalPlayerCount ) +{ + return NULL; +} /* linux_make_serverProxy */ diff --git a/xwords4/linux/linuxserver.h b/xwords4/linux/linuxserver.h new file mode 100644 index 000000000..50fb568d7 --- /dev/null +++ b/xwords4/linux/linuxserver.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _LINUXSERVER_H_ +#define _LINUXSERVER_H_ + +#include "server.h" + +ServerCtxt* linux_make_server( char* dictName, XP_U16 totalPlayerCount ); + +ServerCtxt* linux_make_serverProxy( char* serverAddrStr, + XP_U16 totalPlayerCount ); + + +#endif diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h new file mode 100644 index 000000000..10af129fe --- /dev/null +++ b/xwords4/linux/main.h @@ -0,0 +1,81 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (fixin@peak.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. + */ + +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#include "comtypes.h" +#include "util.h" +#include "game.h" +#include "vtabmgr.h" + +typedef struct ServerInfo { + XP_U16 nRemotePlayers; +/* CommPipeCtxt* pipe; */ +} ServerInfo; + +typedef struct ClientInfo { + char* serverName; /* still need this? */ +} ClientInfo; + +typedef struct LinuxUtilCtxt { + UtilVtable* vtable; +} LinuxUtilCtxt; + +typedef struct LaunchParams { +/* CommPipeCtxt* pipe; */ + XW_UtilCtxt* util; + DictionaryCtxt* dict; + CurGameInfo gi; + char* fileName; + VTableMgr* vtMgr; + XP_U16 nLocalPlayers; + XP_Bool trayOverlaps; /* probably only interesting for GTK case */ + XP_Bool askNewGame; + XP_Bool quitAfter; + XP_Bool sleepOnAnchor; + XP_Bool printHistory; + XP_Bool undoWhenDone; + XP_Bool verticalScore; + // XP_Bool mainParams; + XP_Bool skipWarnings; + XP_Bool showRobotScores; + + Connectedness serverRole; + + union { + ServerInfo serverInfo; + ClientInfo clientInfo; + } info; + + short defaultSendPort; + short defaultListenPort; + +} LaunchParams; + +typedef struct CommonGlobals { + LaunchParams* params; + + XWGame game; + + /* UDP comms stuff */ + char* defaultServerName; +} CommonGlobals; + +#endif diff --git a/xwords4/linux/scripts/playnum.sh b/xwords4/linux/scripts/playnum.sh new file mode 100755 index 000000000..a153a3370 --- /dev/null +++ b/xwords4/linux/scripts/playnum.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# I use this thing this way: playnum.sh 10 2>&1 | ./wordlens.pl + +NUM=$1 +echo "NUM=$NUM" + +while :; do + + ../linux/xwords -u -s -r Eric -d ../linux/dicts/OSPD2to15.xwd -q -i + + NUM=$(( NUM - 1 )); + + if (( $NUM <= 0 )); then exit 0; fi +done \ No newline at end of file diff --git a/xwords4/linux/scripts/wordlens.pl b/xwords4/linux/scripts/wordlens.pl new file mode 100755 index 000000000..2d93911cb --- /dev/null +++ b/xwords4/linux/scripts/wordlens.pl @@ -0,0 +1,23 @@ +#!/usr/bin/perl + +use strict; + +my @counts; +my $nGames = 0; + +while ( <> ) { + chomp; + if ( m|^([A-Z]+) \[| ) { + my $len = length($1); + ++$counts[$len]; + } elsif ( m|^1:1| ) { + ++$nGames; + } +} + +print "****** out of $nGames games: *****\n"; +print "length num played\n"; +for ( my $i = 2; $i <= 15; ++$i ) { + printf( "%3d %8d\n", $i, $counts[$i] ); +} + diff --git a/xwords4/linux/value.xpm b/xwords4/linux/value.xpm new file mode 100644 index 000000000..1bd40e6bd --- /dev/null +++ b/xwords4/linux/value.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * value_xpm[] = { +"8 10 5 1", +" c None", +". c #0F0F0F", +"+ c #2F2F2F", +"@ c #000000", +"# c #FFFFFF", +".+@@@@@@", +"+##@@@#@", +"@.#@@#@@", +"@###@#@@", +"@@@@#@@@", +"@@@#@#@@", +"@@#@#@#@", +"@@#@###@", +"@#@@#@#@", +"@@@@@@@@"}; diff --git a/xwords4/linux/xptypes.h b/xwords4/linux/xptypes.h new file mode 100644 index 000000000..fe3b3347b --- /dev/null +++ b/xwords4/linux/xptypes.h @@ -0,0 +1,113 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (fixin@peak.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. + */ + +#ifndef _XPTYPES_H_ +#define _XPTYPES_H_ + +#include +#include +#include /* memset */ +#include /* memset */ +#include +#include + +#ifdef PLATFORM_GTK +# include +# include +# include +#endif + +#define XP_TRUE ((XP_Bool)(1==1)) +#define XP_FALSE ((XP_Bool)(1==0)) + +typedef unsigned char XP_U8; +typedef signed char XP_S8; +typedef unsigned char XP_UCHAR; + +typedef unsigned short XP_U16; +typedef signed short XP_S16; + +typedef unsigned long XP_U32; +typedef signed long XP_S32; + +typedef signed short XP_FontCode; /* not sure how I'm using this yet */ +typedef unsigned char XP_Bool; + +#ifdef PLATFORM_GTK +typedef guint32 XP_Time; +#else +typedef unsigned long XP_Time; +#endif + +#define XP_CR "\n" + +#define XP_STATUSF XP_DEBUGF +#define XP_LOGF XP_DEBUGF + +#ifdef DEBUG +extern void linux_debugf(char*, ...); +#define XP_DEBUGF linux_debugf +#else +extern void p_ignore(char*, ...); +#define XP_DEBUGF if(0)p_ignore +#endif + +#define XP_WARNF XP_DEBUGF + +#ifdef MEM_DEBUG + +# define XP_PLATMALLOC(nbytes) malloc(nbytes) +# define XP_PLATREALLOC(p,s) realloc((p),(s)) +# define XP_PLATFREE(p) free(p) + +#else + +# define XP_MALLOC(pool,nbytes) malloc(nbytes) +# define XP_REALLOC(pool,p,s) realloc((p),(s)) +# define XP_FREE(pool,p) free(p) +#endif + +#define XP_MEMSET(src, val, nbytes) memset( (src), (val), (nbytes) ) +#define XP_MEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XP_MEMCMP( a1, a2, l ) memcmp((a1),(a2),(l)) +#define XP_STRLEN(s) strlen(s) +#define XP_STRNCMP(s1,s2,len) strncmp((s1),(s2),(len)) +#define XP_STRCMP(s1,s2) strcmp((s1),(s2)) +#define XP_RANDOM() random() +#define XP_SNPRINTF snprintf + +#define XP_MIN(a,b) ((a)<(b)?(a):(b)) +#define XP_MAX(a,b) ((a)>(b)?(a):(b)) + +#ifdef DEBUG +# define XP_ASSERT(b) assert(b) +#else +# define XP_ASSERT(b) +#endif + +#define DGRAM_TYPE SOCK_DGRAM +/* #define DGRAM_TYPE SOCK_STREAM */ + +#define XP_NTOHL(l) ntohl(l) +#define XP_NTOHS(s) ntohs(s) +#define XP_HTONL(l) htonl(l) +#define XP_HTONS(s) htons(s) + +#endif +