Save This Page
Home » openjdk-7 » net.sourceforge » foobase » [javadoc | source]
    1   /* CBFDatabase.java - Implementation of old-style chessbase database.
    2    *
    3    * $Header: M:\\CVSWortel/foobase/net/sourceforge/foobase/CBFDatabase.java,v 1.3 2001/10/07 18:07:41 eddy Exp $
    4    *
    5    * Copyright (c) 2000 Eddy Vanlerberghe.  All rights reserved.
    6    *
    7    * Redistribution and use in source and binary forms, with or without
    8    * modification, are permitted provided that the following conditions
    9    * are met:
   10    * 1. Redistributions of source code must retain the above copyright
   11    *    notice, this list of conditions and the following disclaimer.
   12    * 2. Redistributions in binary form must reproduce the above copyright
   13    *    notice, this list of conditions and the following disclaimer in the
   14    *    documentation and/or other materials provided with the distribution.
   15    * 3. The name of Eddy Vanlerberghe shall not be used to endorse or promote
   16    *    products derived from this software without specific prior written
   17    *    permission.
   18    *
   19    * THIS SOFTWARE IS PROVIDED BY EDDY VANLERBERGHE ``AS IS'' AND
   20    * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   21    * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   22    * ARE DISCLAIMED.  IN NO EVENT SHALL EDDY VANLERBERGHE BE LIABLE
   23    * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   24    * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   25    * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   26    * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   27    * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   28    * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   29    * SUCH DAMAGE.
   30    * 
   31    * Last modification by: $Author: eddy $
   32    * Last modification on: $Date: 2001/10/07 18:07:41 $
   33    * 
   34    * File Revision: $Revision: 1.3 $
   35    * File History:
   36    * $Log: CBFDatabase.java,v $
   37    * Revision 1.3  2001/10/07 18:07:41  eddy
   38    * - Implemented update() and delete() methods
   39    * - Fixed a problem with tournament input parsing
   40    *
   41    * Revision 1.2  2000/12/16 20:39:00  eddy
   42    * Cleaned up code a bit.
   43    *
   44    * Revision 1.1.1.1  2000/12/15 16:27:17  eddy
   45    * First halfway acceptable version.
   46    *
   47    *****************************************************************************/
   48   package net.sourceforge.foobase;
   49   
   50   import java.io;
   51   import java.util;
   52   
   53   /**
   54    * Implementation of old-style chessbase database.
   55    * <p>
   56    * This class is based on the sources of &quot;CB Utilities&quot; by
   57    * Andy Duplain (version of 1-Apr-96, obtained from
   58    * the <a hfref="ftp://ftp.pitt.edu">Pittsburg University</a> FTP site)
   59    * <p>
   60    * None of the code made it directly in this tool (obviously, because the
   61    * original was written C ;-) but nonetheless, much of the information on
   62    * which this source is based on, comes from that toolset.
   63    * <p>
   64    * The structure of the index file (<CODE>.CBI</CODE> file) is like this:
   65    * <p>
   66    * <table>
   67    * <tr><th>Byte-offset</th><th>Length in bytes</th><th>Contents</th></tr>
   68    * <tr><td>0</td><td>4</td><td>Number of games + 1</td></tr>
   69    * <tr><td>...</td><td>...</td><td>...</td></tr>
   70    * <tr><td>4 * (i + 1)</td><td>4</td><td>Location of game i (first game
   71    * is 0) + (i + 2)</td></tr>
   72    * <tr><td>...</td><td>...</td><td>...</td></tr>
   73    * </table>
   74    * <p>
   75    * The last entry in the index table contains the byte offset in the games
   76    * datafile for the next game to enter. E.g. if the database contains 5 games,
   77    * the index file will contain at byte offset 24 (next-game-index ( = 5 + 1)
   78    * times 4 (length of entries in index file)) the 32-bit value
   79    * (byte-length-games-file) + 12 (= 2 times index of next game (-= 5 + 1))
   80    * <p>
   81    * The data in the games file is seriously obfuscated, but the table below
   82    * gives the logical structure without regard for the encoding schemes.
   83    * <p>
   84    * Each game is structured like this:
   85    * <p>
   86    * <ul>
   87    * <li>14 header bytes
   88    * <li>players information
   89    * <li>source information
   90    * <li>actual moves of game encoded like this:
   91    * <p>
   92    * <table border="1">
   93    * <tr><th>Byte-value</th><th>Description</th></tr>
   94    * <tr><td>0xff</td><td>Marks start of a new variation</td></tr>
   95    * <tr><td>0x80</td><td>Marks end of variation</td></tr>
   96    * <tr><td>0x01 - 0x7E</td><td>Number of move from movelist
   97    * (first move is 1)</td></tr>
   98    * <tr><td>0x81 - 0xFE</td><td>Move has comments attached to it: clear high
   99    * bit to obtain regular move number (first move is 1)</td></tr>
  100    * </table>
  101    * <p>
  102    * <li>comments: are encoded in a variable length structure. Each structure
  103    * starts with byte 0xFF and continues up to just before the next byte with
  104    * that value (or until the end of available comment bytes). The bytes contain
  105    * the ASCII values of the annotation text, with the exception of a set of
  106    * special characters that are defined in chessbase specific fonts (mostly
  107    * figurines and evaluation symbols) See also <CODE>CBFUtil</CODE>.
  108    * The list of fields given below may end at any given moment at
  109    * the marker for the next comment block:
  110    * <p>
  111    * <table border="1">
  112    * <tr><th>Byte-length</th><th>Description</th></tr>
  113    * <tr><td>1</td><td>evaluation string (e.g. &quot;blunder&quot;)</td></tr>
  114    * <tr><td>1</td><td>position evalutation string
  115    * (e.g. &quot;with compensation&quot;)</td></tr>
  116    * <tr><td>1</td><td>move evaluation string (e.g.
  117    * &quot;better is&quot;)</td></tr>
  118    * <tr><td>1</td><td>unknown for now: these bytes appear to be
  119    * <CODE>0x00</CODE></td></tr>
  120    * <tr><td>up to just before next 0xFF byte</td><td>Annotation string to
  121    move</td></tr>
  122    * </table>
  123    * <p>
  124    * In order to keep things interesting, there is a feature to store annotated
  125    * text on the game level. If present, such comment is appended to the
  126    * &quot;regular&quot; comments for the game.
  127    * <p>
  128    * <li>initial setup: contains exactly 33 bytes. The first 32 bytes contain
  129    * two board cells each (each cell requires 4 bits) and the last byte contains
  130    * the number of (whole) moves done to reach the specified position. The cells
  131    * are specified row by row and the high nibble goes first. See
  132    * <CODE>Board</CODE> for information on piece encoding in the cells.
  133    * </ul>
  134    *
  135    * @version $Revision: 1.3 $
  136    * @author $Author: eddy $
  137    */
  138   public class CBFDatabase implements IsGameDatabase
  139   {
  140       // {{ Class members }}
  141   
  142       /**
  143        * Switch for toggling debug output.
  144        */
  145       private final static boolean        VERBOSE = false;
  146       /**
  147        * Table for hexadecimal conversion
  148        */
  149       private final static char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
  150                                                 '6', '7', '8', '9', 'A', 'B',
  151                                                 'C', 'D', 'E', 'F'};
  152       /**
  153        * Suffix for index file.
  154        */
  155       private final static String         IDX_TYPE = ".cbi";
  156       /**
  157        * Suffix for moves (games) file.
  158        */
  159       private final static String         IDX_GAMES = ".cbf";
  160       /**
  161        * Position in indexfile of (number-of-games + 1)
  162        */
  163       private final static long           OFSIDX_NEXT_GAME_IDX = 0;
  164       /**
  165        * Length in bytes of game header.
  166        */
  167       private final static int            HEADER_LEN = 14;
  168       /**
  169        * Length for initial board setup.
  170        * <p>
  171        * Note that initial board setups are always of the same length.
  172        */
  173       private final static int    STARTPOS_LEN = 33;
  174       /**
  175        * Start of variationn marker
  176        */
  177       private final static int    START_OF_VARIATION = 0xff;
  178       /**
  179        * End of variation marker
  180        */
  181       private final static int    END_OF_VARIATION = 0x80;
  182       /**
  183        * End of annotation block marker
  184        */
  185       private final static int    END_COMMENT = 0xff;
  186       /**
  187        * Move generator to use for old-style chessbase databases
  188        */
  189       private static CBFMoveGenerator     _dynamo = null;
  190   
  191       // {{ Instance members }}
  192   
  193       /**
  194        * Name of the database.
  195        * <p>
  196        * This name contains both the location (directory) and the pure name
  197        * part of the database.
  198        */
  199       protected String            _dbnam;
  200       /**
  201        * Index file.
  202        */
  203       protected RandomAccessFile  _idx;
  204       /**
  205        * Games data file.
  206        */
  207       protected RandomAccessFile  _gms;
  208   
  209       // {{ Class methods }}
  210   
  211       /**
  212        * Log a message.
  213        *
  214        * @param s <CODE>String</CODE> to log
  215        */
  216       private static void say(String s) {
  217           if (VERBOSE) {
  218               System.err.println("CBFDatabase: " + s);
  219               System.err.flush();
  220           }
  221       }
  222       /**
  223        * Convert a byte array containing ASCII characters to string format.
  224        *
  225        * @param b <CODE>byte[]</CODE> to convert
  226        * @param ofs <CODE>int</CODE> containing the offset to start from
  227        * @param int <CODE>int</CODE> containing the number of bytes to convert
  228        * @return <CODE>String</CODE> containing the converted bytes.
  229        */
  230       private static String b2s(byte[] b, int ofs, int len) {
  231           StringBuffer    sb = new StringBuffer(len);
  232           for (int i = 0; i < len; ++i) {
  233               sb.append((char) b[i + ofs]);
  234           }
  235           return sb.toString();
  236       }
  237       /**
  238        * Convert a byte array containing ASCII characters to string format.
  239        *
  240        * @param b <CODE>byte[]</CODE> to convert
  241        * @return <CODE>String</CODE> containing the converted bytes.
  242        */
  243       private static String b2s(byte[] b) {
  244           return b2s(b, 0, b.length);
  245       }
  246       /**
  247        * Convert a byte array to hex format.
  248        *
  249        * @param b <CODE>byte[]</CODE> to convert
  250        * @param ofs <CODE>int</CODE> containing the offset to start from
  251        * @param int <CODE>int</CODE> containing the number of bytes to convert
  252        * @return <CODE>String</CODE> containing the converted bytes.
  253        */
  254       private static String b2h(byte[] b, int ofs, int len) {
  255           StringBuffer    sb = new StringBuffer(len);
  256           for (int i = 0; i < len; ++i) {
  257               int c = (int) b[i + ofs];
  258               if (i > 0) {
  259                   sb.append(' ');
  260               }
  261               sb.append(HEX_DIGITS[(c >> 4) & 0x0f]);
  262               sb.append(HEX_DIGITS[c & 0x0f]);
  263           }
  264           return sb.toString();
  265       }
  266       /**
  267        * Convert a byte array to hex format.
  268        *
  269        * @param b <CODE>byte[]</CODE> to convert
  270        * @return <CODE>String</CODE> containing the converted bytes.
  271        */
  272       private static String b2h(byte[] b) {
  273           return b2h(b, 0, b.length);
  274       }
  275       /**
  276        * Get single instance of <CODE>CBFMoveGenerator</CODE> class to use
  277        * for move generation.
  278        *
  279        * @return <CODE>CBFMoveGenerator</CODE> to use for old-style chessbase
  280        * databases
  281        */
  282       public static CBFMoveGenerator getMoveGenerator() {
  283           if (_dynamo == null) {
  284               _dynamo = new CBFMoveGenerator();
  285           }
  286           return _dynamo;
  287       }
  288   
  289       // {{ Constructors }}
  290   
  291       /**
  292        * Default public constructor.
  293        */
  294       public CBFDatabase()
  295       {
  296           _dbnam = null;
  297           _idx = null;
  298           _gms = null;
  299       }
  300   
  301       // {{ Instance methods }}
  302   
  303       /**
  304        * Set name of Game collection.
  305        * <p>
  306        * The meaning of this name is implementation dependent, that is, it is
  307        * up to the implementing classes to interprete this name.
  308        * <p>
  309        * For example, this may be the name of a PGN file or it may be the
  310        * basename for an old-style Chessbase database (that will use files with
  311        * this basename followed by the extensions &quot;.CBI&quot; and
  312        * &quot;.CBF&quot;)
  313        *
  314        * @param nam <CODE>String</CODE> specifying the name of the database.
  315        * @exception java.lang.Exception is thrown whenever the name is not
  316        * acceptable.
  317        */
  318       public void setName(String nam) throws Exception {
  319           _dbnam = nam;
  320       }
  321       /**
  322        * Create a new database.
  323        * <p>
  324        * Note that if the database already exists, its index pointers will be
  325        * reset, thus effectively emptying the database.
  326        *
  327        * @exception java.lang.Exception if the database could not be created.
  328        */
  329       public void create() throws Exception {
  330           open(); // Will create empty files in case they do not yet exist
  331           barfInt(_idx, (long) 0, 1);     // Index next game
  332           barfInt(_idx, (long) 4, 2);     // Encoded data offset next game
  333       }
  334       /**
  335        * Open an existing database.
  336        *
  337        * @exception java.lang.Exception if the database could not be opened.
  338        */
  339       public void open() throws Exception {
  340           if (_dbnam == null) {
  341               throw new Exception("CBFDatabase.open() -- " +
  342                                   "missing database name.");
  343           }
  344           _idx = new RandomAccessFile(_dbnam + IDX_TYPE, "rw");
  345           _gms = new RandomAccessFile(_dbnam + IDX_GAMES, "rw");
  346       }
  347       /**
  348        * Returns the number of Games stored in the database.
  349        *
  350        * @return <CODE>int</CODE> containing the number of Games.
  351        * @exception java.lang.Exception if an error occured during the operation.
  352        */
  353       public int size() throws Exception {
  354           return (slurpInt(_idx, OFSIDX_NEXT_GAME_IDX) - 1);
  355       }
  356       /**
  357        * Retrieve all games present in the database.
  358        *
  359        * @return <CODE>Vector</CODE> containing the games present in the
  360        * database.
  361        * @exception java.lang.Exception if an error occured during the operation.
  362        */
  363       public Vector getGames() throws Exception {
  364           Vector  v = new Vector();
  365           int     n = size();
  366           for (int i = 0; i < n; ++i) {
  367               v.addElement(get(i));
  368           }
  369           return v;
  370       }
  371       /**
  372        * Retrieve the headers for all games present in the database.
  373        *
  374        * @return <CODE>Vector</CODE> containing the header strings for all
  375        * the games present in the database.
  376        * @exception java.lang.Exception if an error occured during the operation.
  377        */
  378       public Vector getHeaders() throws Exception {
  379           Vector  v = new Vector();
  380           int     n = size();
  381           for (int i = 0; i < n; ++i) {
  382               String      hdr = getHeader(i);
  383               if (hdr != null) {
  384                   v.addElement(hdr);
  385               }
  386           }
  387           return v;
  388       }
  389       /**
  390        * Retrieve a game header text based on its index in the database.
  391        * <p>
  392        * The information retrieved by this method serves mainly to display
  393        * an overview of the games stored in a database.
  394        * <p>
  395        * Contrary to the <CODE>get()</CODE> method, this method does not throw
  396        * an exception if the specified game is deleted.
  397        *
  398        * @param idx <CODE>int</CODE> zero-based index of the game to retrieve
  399        * @return <CODE>String</CODE> containing the header information of the
  400        * specified game (index number, name of white, name of black) or
  401        * <CODE>null</CODE> in case the game is deleted.
  402        * @exception java.lang.Exception if an error occured during the operation.
  403        */
  404       public String getHeader(int idx) throws Exception {
  405           if ((idx < 0) || (idx >= size())) {
  406               throw new IndexOutOfBoundsException("No game with index " + idx);
  407           }
  408           //
  409           // Offset in games file of game data
  410           //
  411           int     go = slurpInt(_idx, (long) ((idx + 1) * 4)) - idx - 2;
  412           //
  413           // Grab game header bytes
  414           //
  415           byte[]  hdr = slurp(_gms, (long) go, HEADER_LEN);
  416           CBFUtil.decodeHeader(hdr);
  417           if ((hdr[10] & 0x80) != 0) {
  418               //
  419               // Game is deleted.
  420               //
  421               return null;
  422           }
  423           int     res = (int) hdr[1] & 0xff;
  424           int     year = extractYear(hdr);
  425           int     welo = extractWhiteELO(hdr);
  426           int     belo = extractBlackELO(hdr);
  427           String  eco = extractECO(hdr);
  428           int     lenplayers = (int) hdr[4] & 0x3f;
  429           int     lensource =  (int) hdr[5] & 0x3f;
  430           //
  431           // Grab players and so called "source".
  432           //
  433           int     ho = go + HEADER_LEN;
  434           byte[]  hdrtxt = slurp(_gms, (long) ho, lenplayers + lensource);
  435           CBFUtil.decodeHeaderText(hdrtxt);
  436           String[]        infofields = parseHeaderText(hdrtxt, lenplayers);
  437           StringBuffer    sb = new StringBuffer();
  438           sb.append(idx);
  439           sb.append(' ');
  440           if (infofields[2] != null) {
  441               sb.append(infofields[2]);
  442           }
  443           sb.append('-');
  444           if (infofields[3] != null) {
  445               sb.append(infofields[3]);
  446           }
  447           if (infofields[4] != null) {
  448               sb.append('[');
  449               sb.append(infofields[4]);
  450               if (infofields[0] != null) {
  451                   sb.append('(');
  452                   sb.append(infofields[0]);
  453                   sb.append(')');
  454               }
  455               sb.append(']');
  456           }
  457           sb.append(' ');
  458           sb.append(year);
  459           sb.append(' ');
  460           switch (res) {
  461               case 0:
  462                   sb.append("0-1");
  463                   break;
  464               case 1:
  465                   sb.append("1/2");
  466                   break;
  467               case 2:
  468                   sb.append("1-0");
  469                   break;
  470               default:
  471                   sb.append("???");
  472                   break;
  473           }
  474           return sb.toString();
  475       }
  476       /**
  477        * Retrieve a game based on its index in the database.
  478        *
  479        * @param idx <CODE>int</CODE> zero-based index of the game to retrieve
  480        * @return <CODE>Game</CODE> containing the specified game
  481        * @exception java.lang.Exception if an error occured during the operation.
  482        */
  483       public Game get(int idx) throws Exception {
  484           if ((idx < 0) || (idx >= size())) {
  485               throw new IndexOutOfBoundsException("No game with index " + idx);
  486           }
  487           //
  488           // Offset in games file of game data
  489           //
  490           int     go = slurpInt(_idx, (long) ((idx + 1) * 4)) - idx - 2;
  491           //
  492           // Grab game header bytes
  493           //
  494           byte[]  hdr = slurp(_gms, (long) go, HEADER_LEN);
  495           CBFUtil.decodeHeader(hdr);
  496           if ((hdr[10] & 0x80) != 0) {
  497               //
  498               // Game is deleted.
  499               //
  500               throw new Exception("Game " + idx + " is deleted.");
  501           }
  502           Game            g = new Game();
  503           g.setID(new CBFGameID(this, idx));
  504           parseHeader(hdr, g);
  505           int     lenplayers = (int) hdr[4] & 0x3f;
  506           int     lensource =  (int) hdr[5] & 0x3f;
  507           int     lenmoves = (((int) hdr[2] &0xff) << 8 |
  508                               ((int) hdr[3] &0xff)) - 1;
  509           int     lencomm = ((int) hdr[6] &0xff) << 8 | ((int) hdr[7] &0xff);
  510           //
  511           // Grab players and so called "source".
  512           //
  513           int     ho = go + HEADER_LEN;
  514           byte[]  hdrtxt = slurp(_gms, (long) ho, lenplayers + lensource);
  515           CBFUtil.decodeHeaderText(hdrtxt);
  516           parseHeaderText(hdrtxt, lenplayers, g);
  517           int     mo = ho + lenplayers + lensource;
  518           byte[]  moves = null;
  519           if (lenmoves > 0) {
  520               moves = slurp(_gms, (long) mo, lenmoves);
  521           } else {
  522               moves = new byte[0];
  523           }
  524           //
  525           // Process all available annotation data.
  526           //
  527           Vector  notes = null;
  528           int     co = mo + lenmoves;
  529           if (lencomm > 0) {
  530               byte[]      comms = slurp(_gms, (long) co, lencomm);
  531               notes = CBFAnnotation.parse(comms);
  532           } else {
  533               notes = new Vector();
  534           }
  535           Board   board = new Board();
  536           if ((hdr[10] & 0x01) != 0) {
  537               //
  538               // Grab initial board setup
  539               //
  540               int         epmask = ((int) hdr[11]) & 0x0f;
  541               int         ipo = co + lencomm;
  542               byte[]      bar = slurp(_gms, (long) ipo, STARTPOS_LEN);
  543               board.clear();
  544               int         k = 0;
  545               for (int i = 0; i < 8; ++i) {
  546                   for (int j = 0; j < 8; ++j) {
  547                       byte    pies = bar[k++];
  548                       board.setPiece((byte) ((pies >> 4) & 0x0f), i, j++);
  549                       board.setPiece((byte) (pies & 0x0f), i, j);
  550                   }
  551               }
  552               board.setEP(epmask);
  553               if (g.isBlackToMove()) {
  554                   board.setNextHalfMoveNumber(2 * ((int) bar[32] + 1));
  555               } else {
  556                   board.setNextHalfMoveNumber(2 * ((int) bar[32] + 1) - 1);
  557               }
  558               g.setHasInitialBoard(true);
  559           } else {
  560               //
  561               // Use normal initial game setup.
  562               //
  563               g.setHasInitialBoard(false);
  564               board.initialSetup();
  565           }
  566           g.setInitialPosition(board);
  567           //
  568           // The big one: parse moves and comments
  569           //
  570           processMoves(g, moves, notes);
  571           return g;
  572       }
  573       /**
  574        * Add a new Game to the database.
  575        * <p>
  576        * This method is not required to verify insertion of duplicate Games.
  577        * <p>
  578        * Note that the input parameter is changed: its game ID is set.
  579        *
  580        * @param g <CODE>Game</CODE> to add to the database.
  581        * @exception java.lang.Exception if an error occured during the operation.
  582        */
  583       public void add(Game g) throws Exception {
  584           //
  585           // Phase one: prepare byte arrays to dump in the games file.
  586           //
  587           byte[]  hdr = new byte[HEADER_LEN];
  588           byte[]  data = prepareGameForOutput(g, hdr);
  589           //
  590           // Phase two: update both index and games file.
  591           //
  592           // Index next game (1-based)
  593           //
  594           int     idx = slurpInt(_idx, OFSIDX_NEXT_GAME_IDX);
  595           //
  596           // Stuff game ID in Game object
  597           //
  598           g.setID(new CBFGameID(this, idx - 1));
  599           //
  600           // Offset in games file where to write next game
  601           //
  602           // Note: do not subtract 2 for game offset calculation (this ngidx
  603           // is 1-based in stead of zero based!)
  604           //
  605           int     ngidx = idx;
  606           int     ngofs = slurpInt(_idx, (long) (ngidx * 4)) - ngidx - 1;
  607           barf(_gms, (long) ngofs, hdr, 0, hdr.length);
  608           barf(_gms, (long) (ngofs + hdr.length), data, 0, data.length);
  609           ++ngidx;
  610           ngofs += hdr.length + data.length + ngidx + 1;
  611           barfInt(_idx, (long) 0, ngidx);
  612           barfInt(_idx, (long) (ngidx * 4), ngofs);
  613       }
  614       /**
  615        * Prepare buffers to be written to the database.
  616        * <p>
  617        * This method uses a somewhat dubious technique to return two
  618        * pieces of data (well, two byte arrays): one is a fixed size
  619        * array that needs to be pre-allocated and passed to the function as
  620        * an argument, the other one is the function result.
  621        *
  622        * @param g <CODE>Game</CODE> to add to the database.
  623        * @param hdr <CODE>byte[]</CODE> to store the header information in
  624        * (this buffer should contain <CODE>HEADER_LEN</CODE> bytes)
  625        * @return <CODE>byte[]</CODE> containing the data to write to the
  626        * database file (including everything but the
  627        * kitchen&nbsp;sink^H^H^H^H^H^H^H^H^H^H^H^H header)
  628        * @exception java.lang.Exception if an error occured during the operation.
  629        */
  630       protected byte[] prepareGameForOutput(Game g, byte[] hdr)
  631           throws Exception {
  632           //
  633           // For safety reasons it would seem better to first build the entire
  634           // byte array for the game in memory and only when all conversions
  635           // are completed successfully, the array is dumped in file in one
  636           // single swoop.
  637           //
  638           // All intermediate operations are done on this byte array stream.
  639           //
  640           ByteArrayOutputStream   bos = new ByteArrayOutputStream();
  641           byte[]  players = buildPlayersInfo(g);
  642           byte[]  source = buildSourceInfo(g);
  643           byte[]  hdrtxt = new byte[players.length + source.length];
  644           System.arraycopy(players, 0, hdrtxt, 0, players.length);
  645           System.arraycopy(source, 0, hdrtxt, players.length, source.length);
  646           CBFUtil.decodeHeaderText(hdrtxt);
  647           bos.write(hdrtxt);
  648           ByteArrayOutputStream   mvs = new ByteArrayOutputStream();
  649           ByteArrayOutputStream   cms = new ByteArrayOutputStream();
  650           cms.write(END_COMMENT);         // Write initial marker
  651           if (g.getMoves() != null) {
  652               appendMoves(mvs, cms, g.getMoves(), g.getInitialPosition(),
  653                           g.getInitialPosition().getNextHalfMoveNumber());
  654           }
  655           byte[]  mvbyts = mvs.toByteArray();
  656           byte[]  cmbyts = cms.toByteArray();
  657           //
  658           // Obfuscate moves ...
  659           //
  660           int     l = mvbyts.length + 1;
  661           int     mask = l * 49;
  662           int     i = 0;
  663           for (i = l - 2; i > 0; --i) {
  664               mvbyts[i] ^= mask;
  665               mask *= 7;
  666           }
  667           bos.write(mvbyts);
  668           bos.write(cmbyts);
  669           //
  670           // Time to build the game header info.
  671           //
  672           for (i = 0; i < HEADER_LEN; ++i) {
  673               hdr[i] = (byte) 0;  // Zero out header first.
  674           }
  675           Date    dat = g.getDate();
  676           if (dat == null) {
  677               hdr[0] = (byte) 0x7f;
  678           } else {
  679               //
  680               // Store year - 1900
  681               //
  682               hdr[0] = (byte) dat.getYear();
  683           }
  684           hdr[1] = (byte) g.getResult();
  685           hdr[2] = (byte)  (((mvbyts.length + 1) >> 8) & 0xff);
  686           hdr[3] = (byte) ((mvbyts.length + 1) & 0xff);
  687           char    ecoletter = g.getECOLetter();
  688           int     ecomain = g.getECO();
  689           int     eco1 = ((int) ecoletter - (int) 'A') * 100 + ecomain + 1;
  690           hdr[4] = (byte) (((eco1 & 0x60) << 1) | (players.length & 0x3f));
  691           hdr[5] = (byte) (((eco1 & 0x180) >> 1) | (source.length & 0x3f));
  692           hdr[6] = (byte) ((cmbyts.length >> 8) & 0xff);
  693           hdr[7] = (byte) (cmbyts.length & 0xff);
  694           int     we = g.getWhiteELO();
  695           int     be = g.getBlackELO();
  696           //
  697           // Note: this database format does not allow ELO ratings to
  698           // be less than 1600. So, *IF* a rating is given we force it to
  699           // be at least 1600.
  700           //
  701           if (we != 0) {
  702               hdr[8] = (byte) ((((we >= 1600) ? we : 1600) - 1600) / 5);
  703           }
  704           if (be != 0) {
  705               hdr[9] = (byte) ((((be >= 1600) ? be : 1600) - 1600) / 5);
  706           }
  707           //
  708           // WARNING: serious foefeling ahead (on the part of chessbase)
  709           // If an initial game setup was given for a game, it is assumed that
  710           // no ECO info is available and hence some bits can be recycled for
  711           // other purposes. Bit 0x02 of hdr[10] is used either to store which
  712           // player is to move first _OR_ to hold info about the ECO encoding.
  713           //
  714           if (g.hasInitialSetup()) {
  715               int c = (g.isBlackToMove() ? 2 : 0);
  716               hdr[10] = (byte) (c | 0x01);
  717               Board       brd = g.getInitialPosition();
  718               hdr[11] = (byte) brd.getEP();
  719               byte[]      cells = brd.getBoard();
  720               byte[]      bar = new byte[STARTPOS_LEN];
  721               int         k = 0;
  722               for (i = 0; i < 8; ++i) {
  723                   for (int j = 0; j < 8; ++j) {
  724                       c = (cells[(j++ << 3) | i] << 4) | (cells[(j << 3) | i]);
  725                       bar[k++] = (byte) c;
  726                   }
  727               }
  728               int         mvn = brd.getNextHalfMoveNumber();
  729               if (g.isBlackToMove()) {
  730                   cells[32] = (byte) (mvn / 2 - 1);
  731               } else {
  732                   cells[32] = (byte) (((mvn + 1) / 2) - 1);
  733               }
  734               bos.write(cells);
  735           } else {
  736               hdr[10] = (byte) ((eco1 & 0x1f) << 1);
  737               //
  738               // Note: it is not clear what bit 0x40 of hdr[11] is used for.
  739               //
  740               int     subeco = g.getSubECO();
  741               if (subeco != 0) {
  742                   hdr[11] = (byte) (((subeco & 0x40) << 1) | (subeco & 0x3f));
  743               }
  744           }
  745           hdr[12] = (byte) g.getNumberMoves();
  746           CBFUtil.setChecksum(hdr);
  747           CBFUtil.encodeHeader(hdr);
  748           //
  749           // All went according to plan: bail out.
  750           //
  751           return bos.toByteArray();
  752       }
  753       /**
  754        * Update a Game in the database.
  755        * <p>
  756        * Note that this method is only required to work on Games that were
  757        * previously read from the same database as the update is being done.
  758        *
  759        * @param g <CODE>Game</CODE> to update in the database.
  760        * @exception java.lang.Exception if an error occured during the operation.
  761        */
  762       public void update(Game g) throws Exception {
  763           //
  764           // First determine if this game contains a valid ID for the current
  765           // database.
  766           //
  767           int     gmidx = getGameID(g);
  768           if(gmidx < 0) {
  769               throw new Exception("CBFDatabase.update() -- " +
  770                                   "Game does not come from this database.");
  771           }
  772           //
  773           // Ok, game is part of this database.
  774           //
  775           // Phase one: prepare byte arrays to dump in the games file.
  776           //
  777           byte[]  hdr = new byte[HEADER_LEN];
  778           byte[]  data = prepareGameForOutput(g, hdr);
  779           //
  780           // Phase two: update both index and games file.
  781           //
  782           //
  783           // Index next game (1-based)
  784           //
  785           int     ngidx = slurpInt(_idx, OFSIDX_NEXT_GAME_IDX);
  786           //
  787           // Offset in games file where to write next game
  788           //
  789           // Note: do not subtract 2 for game offset calculation (this ngidx
  790           // is 1-based in stead of zero based!)
  791           //
  792           int     ngofs = slurpInt(_idx, (long) (ngidx * 4)) - ngidx - 1;
  793           barf(_gms, (long) ngofs, hdr, 0, hdr.length);
  794           barf(_gms, (long) (ngofs + hdr.length), data, 0, data.length);
  795           //
  796           // Update pointer in games file for this game index
  797           //
  798           barfInt(_idx, (long) ((gmidx + 1) * 4), ngofs + gmidx + 2);
  799           //++ngidx;
  800           ngofs += hdr.length + data.length + ngidx + 1;
  801           //barfInt(_idx, (long) 0, ngidx);
  802           barfInt(_idx, (long) (ngidx * 4), ngofs);
  803       }
  804       /**
  805        * Remove a Game from the database.
  806        * <p>
  807        * Note that this method is only required to work on Games that were
  808        * previously read from the same database as the update is being done.
  809        *
  810        * @param g <CODE>Game</CODE> to remove from the database.
  811        * @exception java.lang.Exception if an error occured during the operation.
  812        */
  813       public void delete(Game g) throws Exception {
  814           //
  815           // First determine if this game contains a valid ID for the current
  816           // database.
  817           //
  818           int     idx = getGameID(g);
  819           if(idx < 0) {
  820               throw new Exception("CBFDatabase.delete() -- " +
  821                                   "Game does not come from this database.");
  822           }
  823           //
  824           // Ok, game is part of this database.
  825           //
  826           // Offset in games file of game data
  827           //
  828           int     go = slurpInt(_idx, (long) ((idx + 1) * 4)) - idx - 2;
  829           //
  830           // Grab game header bytes
  831           //
  832           byte[]  hdr = slurp(_gms, (long) go, HEADER_LEN);
  833           CBFUtil.decodeHeader(hdr);
  834           hdr[10] |= (byte) 0x80; // Force delete flag
  835           CBFUtil.setChecksum(hdr);
  836           CBFUtil.encodeHeader(hdr);
  837           barf(_gms, (long) go, hdr, 0, HEADER_LEN);
  838       }
  839       /**
  840        * Close a database.
  841        *
  842        * @exception java.lang.Exception if an error occured during the operation.
  843        */
  844       public void close() throws Exception {
  845           if ((_idx == null) || (_gms == null)) {
  846               throw new Exception("CBFDatabase.close() -- not properly opened.");
  847           }
  848           _idx.close();
  849           _gms.close();
  850           _idx = null;
  851           _gms = null;
  852       }
  853       /**
  854        * Return the game ID of the specified game in this database.
  855        *
  856        * @param g <CODE>Game</CODE> to test for its presence in this database
  857        * @return <CODE>int</CODE> containing the internal game ID in this
  858        * database for the specified game, or <CODE>-1</CODE> if the game is
  859        * not stored in this database.
  860        */
  861       public int getGameID(Game g) {
  862           int     gmidx = -1;
  863           Vector  ids = g.getIDs();
  864           if(ids != null) {
  865               for (Enumeration e = ids.elements(); e.hasMoreElements(); ) {
  866                   IsGameID    theid = (IsGameID) e.nextElement();
  867                   if(theid instanceof CBFGameID) {
  868                       CBFGameID       cbfid = (CBFGameID) theid;
  869                       if(cbfid.getDatabase() == this) {
  870                           gmidx = cbfid.getGameID();
  871                           break;
  872                       }
  873                   }
  874               }
  875           }
  876           return gmidx;
  877       }
  878       /**
  879        * Extract all relevant information from the game header bytes.
  880        *
  881        * @param hdr <CODE>byte[]</CODE> containing the decoded header bytes read
  882        * from the database
  883        * @param g <CODE>Game</CODE> to initialize with the extracted header
  884        * information
  885        */
  886       protected void parseHeader(byte[] hdr, Game g) {
  887           g.setResult((int) hdr[1] & 0xff);
  888           //
  889           // Old-style databases only keep the year, not the entire date
  890           //
  891           int     y = ((int) hdr[0]) & 0xff;
  892           if (y != 127) {
  893               g.setDate(new Date(y, 0, 1));
  894           }
  895           g.setBlackMoves((hdr[10] & 0x02) != 0);
  896           if (hdr[8] != 0) {
  897               g.setWhiteELO(1600 + 5 * (((int) hdr[8]) & 0xff));
  898           }
  899           if (hdr[9] != 0) {
  900               g.setBlackELO(1600 + 5 * (((int) hdr[9]) & 0xff));
  901           }
  902           int     e = ((((int) hdr[10]) & 0x3e) >> 1) |
  903               ((((int) hdr[4]) & 0xc0) >> 1) |
  904               ((((int) hdr[5]) & 0xc0) << 1);
  905           g.setECOLetter((char) ((int) 'A' + ((e - 1) / 100)));
  906           g.setECO((e - 1) %100);
  907           g.setSubECO((((int) hdr[11]) & 0x3f) |
  908                       ((((int) hdr[11]) & 0x80) >> 1));
  909           g.setNumberMoves(((int) hdr[12]) & 0xff);
  910       }
  911       /**
  912        * Extract year game was played.
  913        *
  914        * @param hdr <CODE>byte[]</CODE> containing the decoded header bytes read
  915        * from the database
  916        * @return <CODE>int</CODE> containing the year that the game was played,
  917        * or <CODE>0</CODE> in case no year information was present in the header
  918        */
  919       protected int extractYear(byte[] hdr) {
  920           int     y = ((int) hdr[0]) & 0xff;
  921           if (y == 127) {
  922               return 0;
  923           } else {
  924               return (y + 1900);
  925           }
  926       }
  927       /**
  928        * Extract ELO of white player.
  929        *
  930        * @param hdr <CODE>byte[]</CODE> containing the decoded header bytes read
  931        * from the database
  932        * @return <CODE>int</CODE> containing the ELO points of white or
  933        * <CODE>0</CODE> in case no such information was present
  934        */
  935       protected int extractWhiteELO(byte[] hdr) {
  936           if (hdr[8] != 0) {
  937               return (1600 + 5 * (((int) hdr[8]) & 0xff));
  938           } else {
  939               return 0;
  940           }
  941       }
  942       /**
  943        * Extract ELO of black player.
  944        *
  945        * @param hdr <CODE>byte[]</CODE> containing the decoded header bytes read
  946        * from the database
  947        * @return <CODE>int</CODE> containing the ELO points of black or
  948        * <CODE>0</CODE> in case no such information was present
  949        */
  950       protected int extractBlackELO(byte[] hdr) {
  951           if (hdr[9] != 0) {
  952               return (1600 + 5 * (((int) hdr[9]) & 0xff));
  953           } else {
  954               return 0;
  955           }
  956       }
  957       /**
  958        * Extract ECO code for game.
  959        *
  960        * @param hdr <CODE>byte[]</CODE> containing the decoded header bytes read
  961        * from the database
  962        * @return <CODE>String</CODE> containing the ECO code for the game
  963        */
  964       protected String extractECO(byte[] hdr) {
  965           int     e = ((((int) hdr[10]) & 0x3e) >> 1) |
  966               ((((int) hdr[4]) & 0xc0) >> 1) |
  967               ((((int) hdr[5]) & 0xc0) << 1);
  968           StringBuffer  out = new StringBuffer();
  969           out.append((char) ((int) 'A' + ((e - 1) / 100)));
  970           out.append((e - 1) %100);
  971           out.append('.');
  972           out.append((((int) hdr[11]) & 0x3f) |
  973                      ((((int) hdr[11]) & 0x80) >> 1));
  974           return out.toString();
  975       }
  976       /**
  977        * Process textual header information such as names of players, tournament
  978        * etc.
  979        *
  980        * @param txt <CODE>byte[]</CODE> containing the decoded game header
  981        * text bytes
  982        * @param plen <CODE>int</CODE> containing the length of the players
  983        * section in <CODE>txt</CODE>
  984        * @param g <CODE>Game</CODE> to store information in
  985        */
  986       protected void parseHeaderText(byte[] txt, int plen, Game g) {
  987           //
  988           // Do actual parsing first
  989           //
  990           String[]        flds = parseHeaderText(txt, plen);
  991           //
  992           // Now stuff all non-null components in the Game object
  993           //
  994           if (flds[0] != null) {
  995               g.setRound(flds[0]);
  996           }
  997           if (flds[1] != null) {
  998               g.setAnnotator(flds[1]);
  999           }
 1000           if (flds[2] != null) {
 1001               g.setWhitePlayer(flds[2]);
 1002           }
 1003           if (flds[3] != null) {
 1004               g.setBlackPlayer(flds[3]);
 1005           }
 1006           if (flds[4] != null) {
 1007               g.setTournament(flds[4]);
 1008           }
 1009       }
 1010       /**
 1011        * Process textual header information such as names of players, tournament
 1012        * etc.
 1013        *
 1014        * @param txt <CODE>byte[]</CODE> containing the decoded game header
 1015        * text bytes
 1016        * @param plen <CODE>int</CODE> containing the length of the players
 1017        * section in <CODE>txt</CODE>
 1018        * @param g <CODE>Game</CODE> to store information in
 1019        * @return <CODE>String[]</CODE> containing the different game info
 1020        * strings. The returned items are like this:
 1021        * <p>
 1022        * <table>
 1023        * <tr><th>Subscript</th><th>Contents</th></tr>
 1024        * <tr><td>0</td><td>Round</td></tr>
 1025        * <tr><td>1</td><td>Annotator</td></tr>
 1026        * <tr><td>2</td><td>Name white player</td></tr>
 1027        * <tr><td>3</td><td>Name black player</td></tr>
 1028        * <tr><td>4</td><td>Tournament</td></tr>
 1029        * </table>
 1030        * <p>
 1031        * Whenever a certain item is not present in the game, its entry is
 1032        * set to <CODE>null</CODE>
 1033        */
 1034       protected String[] parseHeaderText(byte[] txt, int plen) {
 1035           String[]        out = new String[5];
 1036           //
 1037           // The order in which the different components are retrieved does
 1038           // matter, so don't start shuffling the code around ;-)
 1039           //
 1040           out[0] = extractRound(txt);
 1041           out[1] = extractAnnotator(txt, plen);
 1042           //
 1043           // Try to find both players names
 1044           //
 1045           int     l = 0;  // Destination index during trimming, length afterwards
 1046           int     i = 0;  // Source index during trimming
 1047           int     h1 = -1;        // Location first '-'
 1048           int     h2 = -1;        // Location second '-'
 1049           int     cm = -1;        // Location first ','
 1050           for (i = 0; i < plen; ++i) {
 1051               if (Character.isWhitespace((char) txt[i])) {
 1052                   if ((i == l) || !Character.isWhitespace((char) txt[l])) {
 1053                       txt[l] = txt[i];
 1054                       ++l;    // Do *NOT* use autoincrement in previous move !!!!
 1055                   }
 1056               } else {
 1057                   if (((char) txt[i] == '-') && (h1 < 0)) {
 1058                       h1 = l;
 1059                   } else if (((char) txt[i] == '-') && (h2 < 0)) {
 1060                       h2 = l;
 1061                   } else if (((char) txt[i] == ',') && (cm < 0)) {
 1062                       cm = l;
 1063                   }
 1064                   txt[l] = txt[i];
 1065                   ++l;    // Do *NOT* use autoincrement in previous move !!!!
 1066               }
 1067           }
 1068           if ((h1 > 0) && (h2 > 0) && (cm > h1)) {
 1069               h1 = h2;
 1070           }
 1071           if (h1 > 0) {
 1072               out[2] = (CBFUtil.bytesToString(txt, 0, h1).trim());
 1073               if (h1 < l - 2) {
 1074                   out[3] = (CBFUtil.bytesToString(txt, h1 + 1,
 1075                                                          l - h1 - 1).trim());
 1076               }
 1077           } else {
 1078               out[2] = (CBFUtil.bytesToString(txt, 0, l).trim());
 1079               out[3] = null;
 1080           }
 1081           //
 1082           // Tournament info is whatever remains in `source' part
 1083           //
 1084           l = plen;
 1085           int     last_non_space = plen - 1;
 1086           for (i = plen; i < txt.length; ++i) {   // First trim source string
 1087               if (Character.isWhitespace((char) txt[i])) {
 1088                   if ((i == l) || !Character.isWhitespace((char) txt[l])) {
 1089                       txt[l] = txt[i];
 1090                       ++l;    // Do *NOT* use autoincrement in previous move !!!!
 1091                   }
 1092               } else {
 1093                   last_non_space = l;
 1094                   txt[l] = txt[i];
 1095                   ++l;    // Do *NOT* use autoincrement in previous move !!!!
 1096               }
 1097           }
 1098           if(last_non_space >= plen) {
 1099               out[4] = CBFUtil.bytesToString(txt, plen,
 1100                                              last_non_space - plen + 1);
 1101           } else {
 1102               out[4] = null;
 1103           }
 1104           return out;
 1105       }
 1106       /**
 1107        * Extract round information from specified string.
 1108        * <p>
 1109        * Round information is expected to be between parenthesis or brackets,
 1110        * but a slash, followed by a left parenthesis is not a valid round
 1111        * prefix.
 1112        * <p>
 1113        * On return, the input parameter may be changed in that the bytes that
 1114        * contained the round information are set to spaces.
 1115        *
 1116        * @param p <CODE>byte[]</CODE> containing the text to search for round
 1117        * information
 1118        * @return <CODE>String</CODE> containing the round information or
 1119        * <CODE>null</CODE> in case no round information was found.
 1120        */
 1121       protected String extractRound(byte[] p) {
 1122           String  out = null;
 1123           int     i = 0;
 1124           int     a = -1;
 1125           char    mark = '\0';
 1126   
 1127           while (i < p.length) {
 1128               if (((p[i] == '(') && ((i == 0) || (p[i - 1] != '/'))) ||
 1129                   (p[i] == '[')) {
 1130                   mark = ((p[i] == '(') ? ')' : ']');
 1131                   a = i++;
 1132                   if ((p[i] == 'm') && (p[i + 1] == '/')) {
 1133                       //
 1134                       // Skip `m/'
 1135                       //
 1136                       i += 2;
 1137                   }
 1138                   while ((i < p.length) && (p[i] != mark)) {
 1139                       if (Character.isDigit((char) p[i]) || (p[i] == '.')) {
 1140                           ++i;
 1141                       } else {
 1142                           break;
 1143                       }
 1144                   }
 1145                   if ((i < p.length) && (p[i] == mark)) {
 1146                       int l = i - a + 1;
 1147                       if (l > 2) {
 1148                           out = CBFUtil.bytesToString(p, a + 1, l - 2);
 1149                           for (int j = 0; j < l; ++j) {
 1150                               p[j + a] = (byte) ' ';
 1151                           }
 1152                           break;
 1153                       }
 1154                   }
 1155               }
 1156               ++i;
 1157           }
 1158           return out;
 1159       }
 1160       /**
 1161        * Extract annotator information from specified string.
 1162        * <p>
 1163        * Annotator information is expected to be between brackets.
 1164        * <p>
 1165        * On return, the input parameter may be changed.
 1166        *
 1167        * @param p <CODE>byte[]</CODE> containing the text to search for annotator
 1168        * information
 1169        * @param ofs <CODE>int</CODE> containing the byte offset from where to
 1170        * start looking for the annotator.
 1171        * @return <CODE>String</CODE> containing the annotator information or
 1172        * <CODE>null</CODE> in case no annotator information was found.
 1173        */
 1174       protected String extractAnnotator(byte[] p, int ofs) {
 1175           String  out = null;
 1176           int     i = ofs;
 1177           int     a = -1;
 1178   
 1179           while (i < p.length) {
 1180               if (p[i] == '[') {
 1181                   a = i++;
 1182                   while ((i < p.length) && (p[i] != ']')) {
 1183                       ++i;
 1184                   }
 1185                   if ((i < p.length) && (p[i] == ']')) {
 1186                       int l = i - a + 1;
 1187                       if (l > 2) {
 1188                           out = CBFUtil.bytesToString(p, a + 1, l - 2);
 1189                           for (int j = 0; j < l; ++j) {
 1190                               p[j + a] = (byte) ' ';
 1191                           }
 1192                           break;
 1193                       }
 1194                   }
 1195               }
 1196               ++i;
 1197           }
 1198           return out;
 1199       }
 1200       /**
 1201        * Process contents of moves and comments byte arrays and convert them
 1202        * to the proper format as required by this package.
 1203        *
 1204        * @param g <CODE>Game</CODE> to which the moves belong. The properties
 1205        * of this object <strong>will</strong> be changed by this method.
 1206        * @param mvs <CODE>byte[]</CODE> containing the game moves
 1207        * @param notes <CODE>Vector</CODE> containing the move comments. Note that
 1208        * this <CODE>Vector</CODE> is gradually emptied until is is either empty
 1209        * or contains only the game level annotation entry.
 1210        * @exception java.lang.Exception if an error occured during the operation.
 1211        */
 1212       protected void processMoves(Game g, byte[] mvs, Vector notes)
 1213           throws Exception {
 1214           //
 1215           // Unobfuscate moves ...
 1216           //
 1217           int     l = mvs.length + 1;
 1218           int     mask = l * 49;
 1219           for (int i = l - 2; i > 0; --i) {
 1220               mvs[i] ^= mask;
 1221               mask *= 7;
 1222           }
 1223           //
 1224           // Process moves and variations.
 1225           //
 1226           // Note:
 1227           //
 1228           // The original C-code (see comments at class-level) use a
 1229           // recursive scheme for wading through the variations tree. Normally,
 1230           // this would be a naturally method for processing the recursive
 1231           // logical structure in the game movelists. However, since Java
 1232           // discourages so called `side-effects' (i.e. where input parameters
 1233           // are modified by called functions) and the fact that I dislike using
 1234           // global data in multithreaded environment (Java), I decided to
 1235           // flatten out the processing of the moves and their comments (with
 1236           // the help of a handfull of stack objects)
 1237           //
 1238           Board   brd = new Board(g.getInitialPosition());
 1239           int     varlevel = 0;
 1240           int     halfmove = brd.getNextHalfMoveNumber();
 1241           int     m = 0;  // Index next move in `mvs'
 1242           Stack   board_stack = new Stack();      // Contains saved board setups
 1243           Stack   halfmove_stack = new Stack();   // Contains saved halfmove nrs
 1244           Stack   moves_stack = new Stack();      // Contains saved movelists
 1245           boolean has_comment = false;
 1246           Vector  movelist = new Vector();   // Movelist of `current' variation
 1247           Board   prevbrd = null;
 1248           Stack   prvboard_stack = new Stack();   // Boards before start vars.
 1249           while (m < mvs.length) {
 1250               int         move = (int) mvs[m++] & 0xff;
 1251               if (move == END_OF_VARIATION) {
 1252                   //
 1253                   // End of variation reached
 1254                   //
 1255                   if (varlevel == 0) {
 1256                       throw new Exception("Spurious end-variation-marker: " +
 1257                                           "halfmove=" + halfmove);
 1258                   }
 1259                   //
 1260                   // No special handling required on move-level
 1261                   //
 1262                   --varlevel;
 1263                   halfmove = ((Integer) halfmove_stack.pop()).intValue();
 1264                   brd = (Board) board_stack.pop();
 1265                   prevbrd = (Board) prvboard_stack.pop();
 1266                   Vector  v = (Vector) moves_stack.pop();
 1267                   //
 1268                   // Add current movelist (= list of moves in variation) to
 1269                   // variations list of last element in vector `v' (which
 1270                   // contains the moves in the higher level variation)
 1271                   //
 1272                   Move    mv = (Move) v.elementAt(v.size() - 1);
 1273                   mv.addVariation(movelist);
 1274                   movelist = v;   // Continue with higher-level variation
 1275               } else if (move == START_OF_VARIATION) {
 1276                   //
 1277                   // Detected start of variation
 1278                   //
 1279                   // No special handling required on move-level
 1280                   //
 1281                   ++varlevel;
 1282                   halfmove_stack.push(new Integer(halfmove));
 1283                   board_stack.push(brd);  // Pos in main variation
 1284                   prvboard_stack.push(prevbrd);
 1285                   moves_stack.push(movelist);
 1286                   movelist = new Vector();
 1287                   brd = new Board(prevbrd);  // Undo move from main variation
 1288                   --halfmove;
 1289               } else {
 1290                   //
 1291                   // Ordinary move
 1292                   //
 1293                   has_comment = (((move & 0x80) != 0)&& (notes.size() > 0));
 1294                   move = (move &0x7f) - 1;  // Zero based move number
 1295                   //
 1296                   // HACK: all non-zero colors will be considered black
 1297                   //
 1298                   int             c = (halfmove - 1) % 2;
 1299                   Vector          v = brd.generateMoveList(c,
 1300                                                            getMoveGenerator());
 1301                   if (move >= v.size()) {
 1302                       throw new Exception("Houston, we have a problem: move " +
 1303                           move + " does not fit in list of " + v.size() +
 1304                           " valid moves");
 1305                   }
 1306                   Move    newmove = (Move) v.elementAt(move);
 1307                   if (has_comment && (notes.size() > 0)) {
 1308                       //
 1309                       // Grab all sorts of annotation and move evaluations
 1310                       //
 1311                       CBFAnnotation    an = (CBFAnnotation) notes.elementAt(0);
 1312                       an.decorateMove(newmove);
 1313                       notes.removeElementAt(0);
 1314                   }
 1315                   movelist.addElement(newmove);
 1316                   prevbrd = new Board(brd);   // Variations start from this pos.
 1317                   brd.doMove(newmove);
 1318                   ++halfmove;
 1319               }
 1320           }
 1321           //
 1322           // By now `movelist' contains the moves of the main variation (that
 1323           // is: the moves actually played in the game)
 1324           //
 1325           g.setMoves(movelist);
 1326           //
 1327           // Check for game-level annotations.
 1328           //
 1329           if (notes.size() > 0) {
 1330               CBFAnnotation    an = (CBFAnnotation) notes.elementAt(0);
 1331               g.setGameAnnotation(an.getAnnotation());
 1332           }
 1333       }
 1334       /**
 1335        * Build players info part of a game to store in the database.
 1336        *
 1337        * @param g <CODE>Game</CODE> to grab information from
 1338        * @return <CODE>byte[]</CODE> containing the players information
 1339        */
 1340       protected byte[] buildPlayersInfo(Game g) {
 1341           String  s = g.getWhitePlayer() + "-" + g.getBlackPlayer();
 1342           return CBFUtil.stringToBytes(s);
 1343       }
 1344       /**
 1345        * Build game source info (that is: tournament, round and annotator).
 1346        *
 1347        * @param g <CODE>Game</CODE> to grab information from
 1348        * @return <CODE>byte[]</CODE> containing the source information.
 1349        */
 1350       protected byte[] buildSourceInfo(Game g) {
 1351           String  s = "";
 1352           String  t;
 1353           if ((t = g.getTournament()).length() > 0) {
 1354               s = t;
 1355           }
 1356           if ((t = g.getRound()).length() > 0) {
 1357               s += "(" + t + ")";
 1358           }
 1359           if ((t = g.getAnnotataor()).length() > 0) {
 1360               s += "[" + t + "]";
 1361           }
 1362           return CBFUtil.stringToBytes(s);
 1363       }
 1364       /**
 1365        * Build move list for game.
 1366        *
 1367        * @param bos <CODE>ByteArrayOutputStream</CODE> to write moves to
 1368        * @param cos <CODE>ByteArrayOutputStream</CODE> to write comments to
 1369        * @param mvs <CODE>Vector</CODE> containing the variation to process
 1370        * @param brd <CODE>Board</CODE> with setup at beginning of the
 1371        * variation
 1372        * @param halfmove <CODE>int</CODE> containing the number of the halfmove
 1373        * that is at the start of the variation (first move of the game is
 1374        * halfmove one)
 1375        */
 1376       protected void appendMoves(ByteArrayOutputStream bos, 
 1377                                  ByteArrayOutputStream cos, Vector mvs,
 1378                                  Board brd, int halfmove) {
 1379           Board   b = new Board(brd);     // Operate on copy of board
 1380           for (Enumeration e = mvs.elements(); e.hasMoreElements(); ) {
 1381               Object      oo = e.nextElement();
 1382               Move        m = (Move) oo;
 1383               //
 1384               // HACK: all non-zero colors will be considered black
 1385               //
 1386               int         c = (halfmove - 1) % 2;
 1387               Vector      v = b.generateMoveList(c, getMoveGenerator());
 1388               int         mvnr = 0;
 1389               for (Enumeration en = v.elements(); en.hasMoreElements(); ) {
 1390                   Move    mv = (Move) en.nextElement();
 1391                   if (m.equals(mv)) {
 1392                       break;
 1393                   }
 1394                   ++mvnr;
 1395               }
 1396               if (mvnr > v.size()) {
 1397                   System.err.println("Woopsa: move " + m +
 1398                                      " seems fishy in this position:\n" + b +
 1399                       "\n with " + ((c == 0) ? "white" : "black") + " to move");
 1400                   System.err.flush();
 1401                   System.exit(1);
 1402               }
 1403               ++mvnr;     // First move has value 1 in database
 1404               if (m.isAnnotated()) {
 1405                   //
 1406                   // Update comments output.
 1407                   //
 1408                   mvnr |= 0x80;
 1409                   CBFAnnotation.annotationToDBF(cos, m);
 1410               }
 1411               bos.write(mvnr);
 1412               //
 1413               // Done move, check for variations.
 1414               //
 1415               if (m.hasVariations()) {
 1416                   Vector  vars = m.getVariations();
 1417                   bos.write(START_OF_VARIATION);
 1418                   for (Enumeration ee = vars.elements();
 1419                        ee.hasMoreElements(); ) {
 1420                       Vector      branch = (Vector) ee.nextElement();
 1421                       appendMoves(bos, cos, branch, b, halfmove);
 1422                   }
 1423                   bos.write(END_OF_VARIATION);
 1424               }
 1425               //
 1426               // All done for this move: execute on board and try next
 1427               //
 1428               try {
 1429                   b.doMove(m);
 1430               } catch (Exception weirdo) {
 1431                   //
 1432                   // Typical case of `can never happen', so ignore ;-)
 1433                   //
 1434               }
 1435               ++halfmove;
 1436           }
 1437       }
 1438       /**
 1439        * Local helper routine to read and convert 32-bit integer in intel-CPU
 1440        * format to Java representation.
 1441        * <p>
 1442        * The intel format stores a 32-bit 0x12345678 as bytes 0x34, 0x12,
 1443        * 0x78, 0x56.
 1444        *
 1445        * @param f <CODE>RandomAccessFile</CODE> to read from
 1446        * @param o <CODE>long</CODE> offset to read from
 1447        * @return <CODE>int</CODE> containing the java 32-bit integer value
 1448        * @exception IOException if an I/O error occurs
 1449        */
 1450       protected int slurpInt(RandomAccessFile f, long o) throws IOException {
 1451           f.seek(o);
 1452           int     b4 = (int) f.readByte() & 0xff;
 1453           int     b3 = (int) f.readByte() & 0xff;
 1454           int     b2 = (int) f.readByte() & 0xff;
 1455           int     b1 = (int) f.readByte() & 0xff;
 1456           int     i = (((b4 << 8) | b3) << 8 | b2) << 8 | b1;
 1457           return i;
 1458       }
 1459       /**
 1460        * Local helper routine to read and convert 16-bit integer in intel-CPU
 1461        * format to Java representation.
 1462        * <p>
 1463        * The intel format stores a 16-bit 0x1234 as bytes 0x34, 0x12.
 1464        *
 1465        * @param f <CODE>RandomAccessFile</CODE> to read from
 1466        * @param o <CODE>long</CODE> offset to read from
 1467        * @return <CODE>int</CODE> containing the java 16-bit integer value
 1468        * @exception IOException if an I/O error occurs
 1469        */
 1470       protected int slurpShort(RandomAccessFile f, long o) throws IOException {
 1471           f.seek(o);
 1472           int     b2 = (int) f.readByte() & 0xff;
 1473           int     b1 = (int) f.readByte() & 0xff;
 1474           int     i = b2 << 8 | b1;
 1475           return i;
 1476       }
 1477       /**
 1478        * Local helper routine to read and convert unsigned 8-bit integer (i.e.
 1479        * one byte)
 1480        *
 1481        * @param f <CODE>RandomAccessFile</CODE> to read from
 1482        * @param o <CODE>long</CODE> offset to read from
 1483        * @return <CODE>int</CODE> containing the java 8-bit integer value
 1484        * @exception IOException if an I/O error occurs
 1485        */
 1486       protected int slurpByte(RandomAccessFile f, long o) throws IOException {
 1487           f.seek(o);
 1488           return (int) f.readByte() & 0xff;
 1489       }
 1490       /**
 1491        * Local helper method for reading a specified amount of bytes from a file,
 1492        * starting at the specified file offset.
 1493        * <p>
 1494        * Note that this method will block until all requested bytes are read
 1495        * (or until an end-of-file or an exception is detected)
 1496        *
 1497        * @param f <CODE>RandomAccessFile</CODE> to read from
 1498        * @param o <CODE>long</CODE> offset to read from
 1499        * @param len <CODE>int</CODE> number of bytes to read.
 1500        * @return <CODE>byte[]</CODE> of length <CODE>len</CODE> bytes containing
 1501        * the requested data.
 1502        * @exception IOException if an I/O error occurs
 1503        */
 1504       protected byte[] slurp(RandomAccessFile f, long o, int len)
 1505           throws IOException {
 1506           byte[]  bar = new byte[len];
 1507           f.seek(o);
 1508           f.readFully(bar);
 1509           return bar;
 1510       }
 1511       /**
 1512        * Local helper routine to convert 32-bit integer from Java representation
 1513        * to intel-CPU format.
 1514        * <p>
 1515        * The intel format stores a 32-bit 0x12345678 as bytes 0x34, 0x12,
 1516        * 0x78, 0x56.
 1517        *
 1518        * @param f <CODE>RandomAccessFile</CODE> to read from
 1519        * @param o <CODE>long</CODE> offset to read from
 1520        * @param i <CODE>int</CODE> to write
 1521        * @exception IOException if an I/O error occurs
 1522        */
 1523       protected void barfInt(RandomAccessFile f, long o, int i)
 1524           throws IOException {
 1525           int     b1 = i & 0xff;
 1526           i >>= 8;
 1527           int     b2 = i & 0xff;
 1528           i >>= 8;
 1529           int     b3 = i & 0xff;
 1530           i >>= 8;
 1531           int     b4 = i & 0xff;
 1532           f.seek(o);
 1533           f.write(b4);
 1534           f.write(b3);
 1535           f.write(b2);
 1536           f.write(b1);
 1537       }
 1538       /**
 1539        * Write out a 16-bit integer to file at given position and in intel-CPU
 1540        * format.
 1541        * <p>
 1542        * The intel format stores a 16-bit 0x1234 as bytes 0x34, 0x12.
 1543        *
 1544        * @param f <CODE>RandomAccessFile</CODE> to write to
 1545        * @param o <CODE>long</CODE> offset to write to
 1546        * @param s <CODE>int</CODE> 16-bit integer to write
 1547        * @exception IOException if an I/O error occurs
 1548        */
 1549       protected void barfShort(RandomAccessFile f, long o, int s)
 1550           throws IOException {
 1551           int     b2 = (s >> 8) & 0xff;
 1552           int     b1 = s & 0xff;
 1553           int     i = b2 << 8 | b1;
 1554           f.seek(o);
 1555           f.write(b2);
 1556           f.write(b1);
 1557       }
 1558       /**
 1559        * Write out one single byte to file at given position.
 1560        *
 1561        * @param f <CODE>RandomAccessFile</CODE> to write to
 1562        * @param o <CODE>long</CODE> offset to write to
 1563        * @param b <CODE>byte</CODE> to write
 1564        * @exception IOException if an I/O error occurs
 1565        */
 1566       protected void barfByte(RandomAccessFile f, long o, byte b)
 1567           throws IOException {
 1568           f.seek(o);
 1569           f.write(b);
 1570       }
 1571       /**
 1572        * Write specified byte array to file at given position.
 1573        *
 1574        * @param f <CODE>RandomAccessFile</CODE> to write to
 1575        * @param o <CODE>long</CODE> offset to write to
 1576        * @param bar <CODE>byte[]</CODE> to write
 1577        * @param ofs <CODE>int</CODE> offset in <CODE>bar</CODE> from where to
 1578        * write out its contents
 1579        * @param len <CODE>int</CODE> number of bytes to write
 1580        * @exception IOException if an I/O error occurs
 1581        */
 1582       protected void barf(RandomAccessFile f, long o, byte[] bar, int ofs,
 1583                           int len) throws IOException {
 1584           f.seek(o);
 1585           f.write(bar, ofs, len);
 1586       }
 1587       
 1588       // Do not add or change code below this line.
 1589       public static final String __version = "$Revision: 1.3 $";
 1590   }

Save This Page
Home » openjdk-7 » net.sourceforge » foobase » [javadoc | source]