| Method from net.sourceforge.foobase.CBFDatabase Detail: |
public void add(Game g) throws Exception {
//
// Phase one: prepare byte arrays to dump in the games file.
//
byte[] hdr = new byte[HEADER_LEN];
byte[] data = prepareGameForOutput(g, hdr);
//
// Phase two: update both index and games file.
//
// Index next game (1-based)
//
int idx = slurpInt(_idx, OFSIDX_NEXT_GAME_IDX);
//
// Stuff game ID in Game object
//
g.setID(new CBFGameID(this, idx - 1));
//
// Offset in games file where to write next game
//
// Note: do not subtract 2 for game offset calculation (this ngidx
// is 1-based in stead of zero based!)
//
int ngidx = idx;
int ngofs = slurpInt(_idx, (long) (ngidx * 4)) - ngidx - 1;
barf(_gms, (long) ngofs, hdr, 0, hdr.length);
barf(_gms, (long) (ngofs + hdr.length), data, 0, data.length);
++ngidx;
ngofs += hdr.length + data.length + ngidx + 1;
barfInt(_idx, (long) 0, ngidx);
barfInt(_idx, (long) (ngidx * 4), ngofs);
}
Add a new Game to the database.
This method is not required to verify insertion of duplicate Games.
Note that the input parameter is changed: its game ID is set. |
protected void appendMoves(ByteArrayOutputStream bos,
ByteArrayOutputStream cos,
Vector mvs,
Board brd,
int halfmove) {
Board b = new Board(brd); // Operate on copy of board
for (Enumeration e = mvs.elements(); e.hasMoreElements(); ) {
Object oo = e.nextElement();
Move m = (Move) oo;
//
// HACK: all non-zero colors will be considered black
//
int c = (halfmove - 1) % 2;
Vector v = b.generateMoveList(c, getMoveGenerator());
int mvnr = 0;
for (Enumeration en = v.elements(); en.hasMoreElements(); ) {
Move mv = (Move) en.nextElement();
if (m.equals(mv)) {
break;
}
++mvnr;
}
if (mvnr > v.size()) {
System.err.println("Woopsa: move " + m +
" seems fishy in this position:\n" + b +
"\n with " + ((c == 0) ? "white" : "black") + " to move");
System.err.flush();
System.exit(1);
}
++mvnr; // First move has value 1 in database
if (m.isAnnotated()) {
//
// Update comments output.
//
mvnr |= 0x80;
CBFAnnotation.annotationToDBF(cos, m);
}
bos.write(mvnr);
//
// Done move, check for variations.
//
if (m.hasVariations()) {
Vector vars = m.getVariations();
bos.write(START_OF_VARIATION);
for (Enumeration ee = vars.elements();
ee.hasMoreElements(); ) {
Vector branch = (Vector) ee.nextElement();
appendMoves(bos, cos, branch, b, halfmove);
}
bos.write(END_OF_VARIATION);
}
//
// All done for this move: execute on board and try next
//
try {
b.doMove(m);
} catch (Exception weirdo) {
//
// Typical case of `can never happen', so ignore ;-)
//
}
++halfmove;
}
}
Build move list for game. |
protected void barf(RandomAccessFile f,
long o,
byte[] bar,
int ofs,
int len) throws IOException {
f.seek(o);
f.write(bar, ofs, len);
}
Write specified byte array to file at given position. |
protected void barfByte(RandomAccessFile f,
long o,
byte b) throws IOException {
f.seek(o);
f.write(b);
}
Write out one single byte to file at given position. |
protected void barfInt(RandomAccessFile f,
long o,
int i) throws IOException {
int b1 = i & 0xff;
i > >= 8;
int b2 = i & 0xff;
i > >= 8;
int b3 = i & 0xff;
i > >= 8;
int b4 = i & 0xff;
f.seek(o);
f.write(b4);
f.write(b3);
f.write(b2);
f.write(b1);
}
Local helper routine to convert 32-bit integer from Java representation
to intel-CPU format.
The intel format stores a 32-bit 0x12345678 as bytes 0x34, 0x12,
0x78, 0x56. |
protected void barfShort(RandomAccessFile f,
long o,
int s) throws IOException {
int b2 = (s > > 8) & 0xff;
int b1 = s & 0xff;
int i = b2 < < 8 | b1;
f.seek(o);
f.write(b2);
f.write(b1);
}
Write out a 16-bit integer to file at given position and in intel-CPU
format.
The intel format stores a 16-bit 0x1234 as bytes 0x34, 0x12. |
protected byte[] buildPlayersInfo(Game g) {
String s = g.getWhitePlayer() + "-" + g.getBlackPlayer();
return CBFUtil.stringToBytes(s);
}
Build players info part of a game to store in the database. |
protected byte[] buildSourceInfo(Game g) {
String s = "";
String t;
if ((t = g.getTournament()).length() > 0) {
s = t;
}
if ((t = g.getRound()).length() > 0) {
s += "(" + t + ")";
}
if ((t = g.getAnnotataor()).length() > 0) {
s += "[" + t + "]";
}
return CBFUtil.stringToBytes(s);
}
Build game source info (that is: tournament, round and annotator). |
public void close() throws Exception {
if ((_idx == null) || (_gms == null)) {
throw new Exception("CBFDatabase.close() -- not properly opened.");
}
_idx.close();
_gms.close();
_idx = null;
_gms = null;
}
|
public void create() throws Exception {
open(); // Will create empty files in case they do not yet exist
barfInt(_idx, (long) 0, 1); // Index next game
barfInt(_idx, (long) 4, 2); // Encoded data offset next game
}
Create a new database.
Note that if the database already exists, its index pointers will be
reset, thus effectively emptying the database. |
public void delete(Game g) throws Exception {
//
// First determine if this game contains a valid ID for the current
// database.
//
int idx = getGameID(g);
if(idx < 0) {
throw new Exception("CBFDatabase.delete() -- " +
"Game does not come from this database.");
}
//
// Ok, game is part of this database.
//
// Offset in games file of game data
//
int go = slurpInt(_idx, (long) ((idx + 1) * 4)) - idx - 2;
//
// Grab game header bytes
//
byte[] hdr = slurp(_gms, (long) go, HEADER_LEN);
CBFUtil.decodeHeader(hdr);
hdr[10] |= (byte) 0x80; // Force delete flag
CBFUtil.setChecksum(hdr);
CBFUtil.encodeHeader(hdr);
barf(_gms, (long) go, hdr, 0, HEADER_LEN);
}
Remove a Game from the database.
Note that this method is only required to work on Games that were
previously read from the same database as the update is being done. |
protected String extractAnnotator(byte[] p,
int ofs) {
String out = null;
int i = ofs;
int a = -1;
while (i < p.length) {
if (p[i] == '[") {
a = i++;
while ((i < p.length) && (p[i] != ']")) {
++i;
}
if ((i < p.length) && (p[i] == ']")) {
int l = i - a + 1;
if (l > 2) {
out = CBFUtil.bytesToString(p, a + 1, l - 2);
for (int j = 0; j < l; ++j) {
p[j + a] = (byte) ' ";
}
break;
}
}
}
++i;
}
return out;
}
Extract annotator information from specified string.
Annotator information is expected to be between brackets.
On return, the input parameter may be changed. |
protected int extractBlackELO(byte[] hdr) {
if (hdr[9] != 0) {
return (1600 + 5 * (((int) hdr[9]) & 0xff));
} else {
return 0;
}
}
Extract ELO of black player. |
protected String extractECO(byte[] hdr) {
int e = ((((int) hdr[10]) & 0x3e) > > 1) |
((((int) hdr[4]) & 0xc0) > > 1) |
((((int) hdr[5]) & 0xc0) < < 1);
StringBuffer out = new StringBuffer();
out.append((char) ((int) 'A" + ((e - 1) / 100)));
out.append((e - 1) %100);
out.append('.");
out.append((((int) hdr[11]) & 0x3f) |
((((int) hdr[11]) & 0x80) > > 1));
return out.toString();
}
Extract ECO code for game. |
protected String extractRound(byte[] p) {
String out = null;
int i = 0;
int a = -1;
char mark = '\0";
while (i < p.length) {
if (((p[i] == '(") && ((i == 0) || (p[i - 1] != '/"))) ||
(p[i] == '[")) {
mark = ((p[i] == '(") ? ')" : ']");
a = i++;
if ((p[i] == 'm") && (p[i + 1] == '/")) {
//
// Skip `m/'
//
i += 2;
}
while ((i < p.length) && (p[i] != mark)) {
if (Character.isDigit((char) p[i]) || (p[i] == '.")) {
++i;
} else {
break;
}
}
if ((i < p.length) && (p[i] == mark)) {
int l = i - a + 1;
if (l > 2) {
out = CBFUtil.bytesToString(p, a + 1, l - 2);
for (int j = 0; j < l; ++j) {
p[j + a] = (byte) ' ";
}
break;
}
}
}
++i;
}
return out;
}
Extract round information from specified string.
Round information is expected to be between parenthesis or brackets,
but a slash, followed by a left parenthesis is not a valid round
prefix.
On return, the input parameter may be changed in that the bytes that
contained the round information are set to spaces. |
protected int extractWhiteELO(byte[] hdr) {
if (hdr[8] != 0) {
return (1600 + 5 * (((int) hdr[8]) & 0xff));
} else {
return 0;
}
}
Extract ELO of white player. |
protected int extractYear(byte[] hdr) {
int y = ((int) hdr[0]) & 0xff;
if (y == 127) {
return 0;
} else {
return (y + 1900);
}
}
Extract year game was played. |
public Game get(int idx) throws Exception {
if ((idx < 0) || (idx >= size())) {
throw new IndexOutOfBoundsException("No game with index " + idx);
}
//
// Offset in games file of game data
//
int go = slurpInt(_idx, (long) ((idx + 1) * 4)) - idx - 2;
//
// Grab game header bytes
//
byte[] hdr = slurp(_gms, (long) go, HEADER_LEN);
CBFUtil.decodeHeader(hdr);
if ((hdr[10] & 0x80) != 0) {
//
// Game is deleted.
//
throw new Exception("Game " + idx + " is deleted.");
}
Game g = new Game();
g.setID(new CBFGameID(this, idx));
parseHeader(hdr, g);
int lenplayers = (int) hdr[4] & 0x3f;
int lensource = (int) hdr[5] & 0x3f;
int lenmoves = (((int) hdr[2] &0xff) < < 8 |
((int) hdr[3] &0xff)) - 1;
int lencomm = ((int) hdr[6] &0xff) < < 8 | ((int) hdr[7] &0xff);
//
// Grab players and so called "source".
//
int ho = go + HEADER_LEN;
byte[] hdrtxt = slurp(_gms, (long) ho, lenplayers + lensource);
CBFUtil.decodeHeaderText(hdrtxt);
parseHeaderText(hdrtxt, lenplayers, g);
int mo = ho + lenplayers + lensource;
byte[] moves = null;
if (lenmoves > 0) {
moves = slurp(_gms, (long) mo, lenmoves);
} else {
moves = new byte[0];
}
//
// Process all available annotation data.
//
Vector notes = null;
int co = mo + lenmoves;
if (lencomm > 0) {
byte[] comms = slurp(_gms, (long) co, lencomm);
notes = CBFAnnotation.parse(comms);
} else {
notes = new Vector();
}
Board board = new Board();
if ((hdr[10] & 0x01) != 0) {
//
// Grab initial board setup
//
int epmask = ((int) hdr[11]) & 0x0f;
int ipo = co + lencomm;
byte[] bar = slurp(_gms, (long) ipo, STARTPOS_LEN);
board.clear();
int k = 0;
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; ++j) {
byte pies = bar[k++];
board.setPiece((byte) ((pies > > 4) & 0x0f), i, j++);
board.setPiece((byte) (pies & 0x0f), i, j);
}
}
board.setEP(epmask);
if (g.isBlackToMove()) {
board.setNextHalfMoveNumber(2 * ((int) bar[32] + 1));
} else {
board.setNextHalfMoveNumber(2 * ((int) bar[32] + 1) - 1);
}
g.setHasInitialBoard(true);
} else {
//
// Use normal initial game setup.
//
g.setHasInitialBoard(false);
board.initialSetup();
}
g.setInitialPosition(board);
//
// The big one: parse moves and comments
//
processMoves(g, moves, notes);
return g;
}
Retrieve a game based on its index in the database. |
public int getGameID(Game g) {
int gmidx = -1;
Vector ids = g.getIDs();
if(ids != null) {
for (Enumeration e = ids.elements(); e.hasMoreElements(); ) {
IsGameID theid = (IsGameID) e.nextElement();
if(theid instanceof CBFGameID) {
CBFGameID cbfid = (CBFGameID) theid;
if(cbfid.getDatabase() == this) {
gmidx = cbfid.getGameID();
break;
}
}
}
}
return gmidx;
}
Return the game ID of the specified game in this database. |
public Vector getGames() throws Exception {
Vector v = new Vector();
int n = size();
for (int i = 0; i < n; ++i) {
v.addElement(get(i));
}
return v;
}
Retrieve all games present in the database. |
public String getHeader(int idx) throws Exception {
if ((idx < 0) || (idx >= size())) {
throw new IndexOutOfBoundsException("No game with index " + idx);
}
//
// Offset in games file of game data
//
int go = slurpInt(_idx, (long) ((idx + 1) * 4)) - idx - 2;
//
// Grab game header bytes
//
byte[] hdr = slurp(_gms, (long) go, HEADER_LEN);
CBFUtil.decodeHeader(hdr);
if ((hdr[10] & 0x80) != 0) {
//
// Game is deleted.
//
return null;
}
int res = (int) hdr[1] & 0xff;
int year = extractYear(hdr);
int welo = extractWhiteELO(hdr);
int belo = extractBlackELO(hdr);
String eco = extractECO(hdr);
int lenplayers = (int) hdr[4] & 0x3f;
int lensource = (int) hdr[5] & 0x3f;
//
// Grab players and so called "source".
//
int ho = go + HEADER_LEN;
byte[] hdrtxt = slurp(_gms, (long) ho, lenplayers + lensource);
CBFUtil.decodeHeaderText(hdrtxt);
String[] infofields = parseHeaderText(hdrtxt, lenplayers);
StringBuffer sb = new StringBuffer();
sb.append(idx);
sb.append(' ");
if (infofields[2] != null) {
sb.append(infofields[2]);
}
sb.append('-");
if (infofields[3] != null) {
sb.append(infofields[3]);
}
if (infofields[4] != null) {
sb.append('[");
sb.append(infofields[4]);
if (infofields[0] != null) {
sb.append('(");
sb.append(infofields[0]);
sb.append(')");
}
sb.append(']");
}
sb.append(' ");
sb.append(year);
sb.append(' ");
switch (res) {
case 0:
sb.append("0-1");
break;
case 1:
sb.append("1/2");
break;
case 2:
sb.append("1-0");
break;
default:
sb.append("???");
break;
}
return sb.toString();
}
Retrieve a game header text based on its index in the database.
The information retrieved by this method serves mainly to display
an overview of the games stored in a database.
Contrary to the get() method, this method does not throw
an exception if the specified game is deleted. |
public Vector getHeaders() throws Exception {
Vector v = new Vector();
int n = size();
for (int i = 0; i < n; ++i) {
String hdr = getHeader(i);
if (hdr != null) {
v.addElement(hdr);
}
}
return v;
}
Retrieve the headers for all games present in the database. |
public static CBFMoveGenerator getMoveGenerator() {
if (_dynamo == null) {
_dynamo = new CBFMoveGenerator();
}
return _dynamo;
}
Get single instance of CBFMoveGenerator class to use
for move generation. |
public void open() throws Exception {
if (_dbnam == null) {
throw new Exception("CBFDatabase.open() -- " +
"missing database name.");
}
_idx = new RandomAccessFile(_dbnam + IDX_TYPE, "rw");
_gms = new RandomAccessFile(_dbnam + IDX_GAMES, "rw");
}
Open an existing database. |
protected void parseHeader(byte[] hdr,
Game g) {
g.setResult((int) hdr[1] & 0xff);
//
// Old-style databases only keep the year, not the entire date
//
int y = ((int) hdr[0]) & 0xff;
if (y != 127) {
g.setDate(new Date(y, 0, 1));
}
g.setBlackMoves((hdr[10] & 0x02) != 0);
if (hdr[8] != 0) {
g.setWhiteELO(1600 + 5 * (((int) hdr[8]) & 0xff));
}
if (hdr[9] != 0) {
g.setBlackELO(1600 + 5 * (((int) hdr[9]) & 0xff));
}
int e = ((((int) hdr[10]) & 0x3e) > > 1) |
((((int) hdr[4]) & 0xc0) > > 1) |
((((int) hdr[5]) & 0xc0) < < 1);
g.setECOLetter((char) ((int) 'A" + ((e - 1) / 100)));
g.setECO((e - 1) %100);
g.setSubECO((((int) hdr[11]) & 0x3f) |
((((int) hdr[11]) & 0x80) > > 1));
g.setNumberMoves(((int) hdr[12]) & 0xff);
}
Extract all relevant information from the game header bytes. |
protected String[] parseHeaderText(byte[] txt,
int plen) {
String[] out = new String[5];
//
// The order in which the different components are retrieved does
// matter, so don't start shuffling the code around ;-)
//
out[0] = extractRound(txt);
out[1] = extractAnnotator(txt, plen);
//
// Try to find both players names
//
int l = 0; // Destination index during trimming, length afterwards
int i = 0; // Source index during trimming
int h1 = -1; // Location first '-'
int h2 = -1; // Location second '-'
int cm = -1; // Location first ','
for (i = 0; i < plen; ++i) {
if (Character.isWhitespace((char) txt[i])) {
if ((i == l) || !Character.isWhitespace((char) txt[l])) {
txt[l] = txt[i];
++l; // Do *NOT* use autoincrement in previous move !!!!
}
} else {
if (((char) txt[i] == '-") && (h1 < 0)) {
h1 = l;
} else if (((char) txt[i] == '-") && (h2 < 0)) {
h2 = l;
} else if (((char) txt[i] == ',") && (cm < 0)) {
cm = l;
}
txt[l] = txt[i];
++l; // Do *NOT* use autoincrement in previous move !!!!
}
}
if ((h1 > 0) && (h2 > 0) && (cm > h1)) {
h1 = h2;
}
if (h1 > 0) {
out[2] = (CBFUtil.bytesToString(txt, 0, h1).trim());
if (h1 < l - 2) {
out[3] = (CBFUtil.bytesToString(txt, h1 + 1,
l - h1 - 1).trim());
}
} else {
out[2] = (CBFUtil.bytesToString(txt, 0, l).trim());
out[3] = null;
}
//
// Tournament info is whatever remains in `source' part
//
l = plen;
int last_non_space = plen - 1;
for (i = plen; i < txt.length; ++i) { // First trim source string
if (Character.isWhitespace((char) txt[i])) {
if ((i == l) || !Character.isWhitespace((char) txt[l])) {
txt[l] = txt[i];
++l; // Do *NOT* use autoincrement in previous move !!!!
}
} else {
last_non_space = l;
txt[l] = txt[i];
++l; // Do *NOT* use autoincrement in previous move !!!!
}
}
if(last_non_space >= plen) {
out[4] = CBFUtil.bytesToString(txt, plen,
last_non_space - plen + 1);
} else {
out[4] = null;
}
return out;
}
Process textual header information such as names of players, tournament
etc. |
protected void parseHeaderText(byte[] txt,
int plen,
Game g) {
//
// Do actual parsing first
//
String[] flds = parseHeaderText(txt, plen);
//
// Now stuff all non-null components in the Game object
//
if (flds[0] != null) {
g.setRound(flds[0]);
}
if (flds[1] != null) {
g.setAnnotator(flds[1]);
}
if (flds[2] != null) {
g.setWhitePlayer(flds[2]);
}
if (flds[3] != null) {
g.setBlackPlayer(flds[3]);
}
if (flds[4] != null) {
g.setTournament(flds[4]);
}
}
Process textual header information such as names of players, tournament
etc. |
protected byte[] prepareGameForOutput(Game g,
byte[] hdr) throws Exception {
//
// For safety reasons it would seem better to first build the entire
// byte array for the game in memory and only when all conversions
// are completed successfully, the array is dumped in file in one
// single swoop.
//
// All intermediate operations are done on this byte array stream.
//
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] players = buildPlayersInfo(g);
byte[] source = buildSourceInfo(g);
byte[] hdrtxt = new byte[players.length + source.length];
System.arraycopy(players, 0, hdrtxt, 0, players.length);
System.arraycopy(source, 0, hdrtxt, players.length, source.length);
CBFUtil.decodeHeaderText(hdrtxt);
bos.write(hdrtxt);
ByteArrayOutputStream mvs = new ByteArrayOutputStream();
ByteArrayOutputStream cms = new ByteArrayOutputStream();
cms.write(END_COMMENT); // Write initial marker
if (g.getMoves() != null) {
appendMoves(mvs, cms, g.getMoves(), g.getInitialPosition(),
g.getInitialPosition().getNextHalfMoveNumber());
}
byte[] mvbyts = mvs.toByteArray();
byte[] cmbyts = cms.toByteArray();
//
// Obfuscate moves ...
//
int l = mvbyts.length + 1;
int mask = l * 49;
int i = 0;
for (i = l - 2; i > 0; --i) {
mvbyts[i] ^= mask;
mask *= 7;
}
bos.write(mvbyts);
bos.write(cmbyts);
//
// Time to build the game header info.
//
for (i = 0; i < HEADER_LEN; ++i) {
hdr[i] = (byte) 0; // Zero out header first.
}
Date dat = g.getDate();
if (dat == null) {
hdr[0] = (byte) 0x7f;
} else {
//
// Store year - 1900
//
hdr[0] = (byte) dat.getYear();
}
hdr[1] = (byte) g.getResult();
hdr[2] = (byte) (((mvbyts.length + 1) > > 8) & 0xff);
hdr[3] = (byte) ((mvbyts.length + 1) & 0xff);
char ecoletter = g.getECOLetter();
int ecomain = g.getECO();
int eco1 = ((int) ecoletter - (int) 'A") * 100 + ecomain + 1;
hdr[4] = (byte) (((eco1 & 0x60) < < 1) | (players.length & 0x3f));
hdr[5] = (byte) (((eco1 & 0x180) > > 1) | (source.length & 0x3f));
hdr[6] = (byte) ((cmbyts.length > > 8) & 0xff);
hdr[7] = (byte) (cmbyts.length & 0xff);
int we = g.getWhiteELO();
int be = g.getBlackELO();
//
// Note: this database format does not allow ELO ratings to
// be less than 1600. So, *IF* a rating is given we force it to
// be at least 1600.
//
if (we != 0) {
hdr[8] = (byte) ((((we >= 1600) ? we : 1600) - 1600) / 5);
}
if (be != 0) {
hdr[9] = (byte) ((((be >= 1600) ? be : 1600) - 1600) / 5);
}
//
// WARNING: serious foefeling ahead (on the part of chessbase)
// If an initial game setup was given for a game, it is assumed that
// no ECO info is available and hence some bits can be recycled for
// other purposes. Bit 0x02 of hdr[10] is used either to store which
// player is to move first _OR_ to hold info about the ECO encoding.
//
if (g.hasInitialSetup()) {
int c = (g.isBlackToMove() ? 2 : 0);
hdr[10] = (byte) (c | 0x01);
Board brd = g.getInitialPosition();
hdr[11] = (byte) brd.getEP();
byte[] cells = brd.getBoard();
byte[] bar = new byte[STARTPOS_LEN];
int k = 0;
for (i = 0; i < 8; ++i) {
for (int j = 0; j < 8; ++j) {
c = (cells[(j++ < < 3) | i] < < 4) | (cells[(j < < 3) | i]);
bar[k++] = (byte) c;
}
}
int mvn = brd.getNextHalfMoveNumber();
if (g.isBlackToMove()) {
cells[32] = (byte) (mvn / 2 - 1);
} else {
cells[32] = (byte) (((mvn + 1) / 2) - 1);
}
bos.write(cells);
} else {
hdr[10] = (byte) ((eco1 & 0x1f) < < 1);
//
// Note: it is not clear what bit 0x40 of hdr[11] is used for.
//
int subeco = g.getSubECO();
if (subeco != 0) {
hdr[11] = (byte) (((subeco & 0x40) < < 1) | (subeco & 0x3f));
}
}
hdr[12] = (byte) g.getNumberMoves();
CBFUtil.setChecksum(hdr);
CBFUtil.encodeHeader(hdr);
//
// All went according to plan: bail out.
//
return bos.toByteArray();
}
Prepare buffers to be written to the database.
This method uses a somewhat dubious technique to return two
pieces of data (well, two byte arrays): one is a fixed size
array that needs to be pre-allocated and passed to the function as
an argument, the other one is the function result. |
protected void processMoves(Game g,
byte[] mvs,
Vector notes) throws Exception {
//
// Unobfuscate moves ...
//
int l = mvs.length + 1;
int mask = l * 49;
for (int i = l - 2; i > 0; --i) {
mvs[i] ^= mask;
mask *= 7;
}
//
// Process moves and variations.
//
// Note:
//
// The original C-code (see comments at class-level) use a
// recursive scheme for wading through the variations tree. Normally,
// this would be a naturally method for processing the recursive
// logical structure in the game movelists. However, since Java
// discourages so called `side-effects' (i.e. where input parameters
// are modified by called functions) and the fact that I dislike using
// global data in multithreaded environment (Java), I decided to
// flatten out the processing of the moves and their comments (with
// the help of a handfull of stack objects)
//
Board brd = new Board(g.getInitialPosition());
int varlevel = 0;
int halfmove = brd.getNextHalfMoveNumber();
int m = 0; // Index next move in `mvs'
Stack board_stack = new Stack(); // Contains saved board setups
Stack halfmove_stack = new Stack(); // Contains saved halfmove nrs
Stack moves_stack = new Stack(); // Contains saved movelists
boolean has_comment = false;
Vector movelist = new Vector(); // Movelist of `current' variation
Board prevbrd = null;
Stack prvboard_stack = new Stack(); // Boards before start vars.
while (m < mvs.length) {
int move = (int) mvs[m++] & 0xff;
if (move == END_OF_VARIATION) {
//
// End of variation reached
//
if (varlevel == 0) {
throw new Exception("Spurious end-variation-marker: " +
"halfmove=" + halfmove);
}
//
// No special handling required on move-level
//
--varlevel;
halfmove = ((Integer) halfmove_stack.pop()).intValue();
brd = (Board) board_stack.pop();
prevbrd = (Board) prvboard_stack.pop();
Vector v = (Vector) moves_stack.pop();
//
// Add current movelist (= list of moves in variation) to
// variations list of last element in vector `v' (which
// contains the moves in the higher level variation)
//
Move mv = (Move) v.elementAt(v.size() - 1);
mv.addVariation(movelist);
movelist = v; // Continue with higher-level variation
} else if (move == START_OF_VARIATION) {
//
// Detected start of variation
//
// No special handling required on move-level
//
++varlevel;
halfmove_stack.push(new Integer(halfmove));
board_stack.push(brd); // Pos in main variation
prvboard_stack.push(prevbrd);
moves_stack.push(movelist);
movelist = new Vector();
brd = new Board(prevbrd); // Undo move from main variation
--halfmove;
} else {
//
// Ordinary move
//
has_comment = (((move & 0x80) != 0)&& (notes.size() > 0));
move = (move &0x7f) - 1; // Zero based move number
//
// HACK: all non-zero colors will be considered black
//
int c = (halfmove - 1) % 2;
Vector v = brd.generateMoveList(c,
getMoveGenerator());
if (move >= v.size()) {
throw new Exception("Houston, we have a problem: move " +
move + " does not fit in list of " + v.size() +
" valid moves");
}
Move newmove = (Move) v.elementAt(move);
if (has_comment && (notes.size() > 0)) {
//
// Grab all sorts of annotation and move evaluations
//
CBFAnnotation an = (CBFAnnotation) notes.elementAt(0);
an.decorateMove(newmove);
notes.removeElementAt(0);
}
movelist.addElement(newmove);
prevbrd = new Board(brd); // Variations start from this pos.
brd.doMove(newmove);
++halfmove;
}
}
//
// By now `movelist' contains the moves of the main variation (that
// is: the moves actually played in the game)
//
g.setMoves(movelist);
//
// Check for game-level annotations.
//
if (notes.size() > 0) {
CBFAnnotation an = (CBFAnnotation) notes.elementAt(0);
g.setGameAnnotation(an.getAnnotation());
}
}
Process contents of moves and comments byte arrays and convert them
to the proper format as required by this package. |
public void setName(String nam) throws Exception {
_dbnam = nam;
}
Set name of Game collection.
The meaning of this name is implementation dependent, that is, it is
up to the implementing classes to interprete this name.
For example, this may be the name of a PGN file or it may be the
basename for an old-style Chessbase database (that will use files with
this basename followed by the extensions ".CBI" and
".CBF") |
public int size() throws Exception {
return (slurpInt(_idx, OFSIDX_NEXT_GAME_IDX) - 1);
}
Returns the number of Games stored in the database. |
protected byte[] slurp(RandomAccessFile f,
long o,
int len) throws IOException {
byte[] bar = new byte[len];
f.seek(o);
f.readFully(bar);
return bar;
}
Local helper method for reading a specified amount of bytes from a file,
starting at the specified file offset.
Note that this method will block until all requested bytes are read
(or until an end-of-file or an exception is detected) |
protected int slurpByte(RandomAccessFile f,
long o) throws IOException {
f.seek(o);
return (int) f.readByte() & 0xff;
}
Local helper routine to read and convert unsigned 8-bit integer (i.e.
one byte) |
protected int slurpInt(RandomAccessFile f,
long o) throws IOException {
f.seek(o);
int b4 = (int) f.readByte() & 0xff;
int b3 = (int) f.readByte() & 0xff;
int b2 = (int) f.readByte() & 0xff;
int b1 = (int) f.readByte() & 0xff;
int i = (((b4 < < 8) | b3) < < 8 | b2) < < 8 | b1;
return i;
}
Local helper routine to read and convert 32-bit integer in intel-CPU
format to Java representation.
The intel format stores a 32-bit 0x12345678 as bytes 0x34, 0x12,
0x78, 0x56. |
protected int slurpShort(RandomAccessFile f,
long o) throws IOException {
f.seek(o);
int b2 = (int) f.readByte() & 0xff;
int b1 = (int) f.readByte() & 0xff;
int i = b2 < < 8 | b1;
return i;
}
Local helper routine to read and convert 16-bit integer in intel-CPU
format to Java representation.
The intel format stores a 16-bit 0x1234 as bytes 0x34, 0x12. |
public void update(Game g) throws Exception {
//
// First determine if this game contains a valid ID for the current
// database.
//
int gmidx = getGameID(g);
if(gmidx < 0) {
throw new Exception("CBFDatabase.update() -- " +
"Game does not come from this database.");
}
//
// Ok, game is part of this database.
//
// Phase one: prepare byte arrays to dump in the games file.
//
byte[] hdr = new byte[HEADER_LEN];
byte[] data = prepareGameForOutput(g, hdr);
//
// Phase two: update both index and games file.
//
//
// Index next game (1-based)
//
int ngidx = slurpInt(_idx, OFSIDX_NEXT_GAME_IDX);
//
// Offset in games file where to write next game
//
// Note: do not subtract 2 for game offset calculation (this ngidx
// is 1-based in stead of zero based!)
//
int ngofs = slurpInt(_idx, (long) (ngidx * 4)) - ngidx - 1;
barf(_gms, (long) ngofs, hdr, 0, hdr.length);
barf(_gms, (long) (ngofs + hdr.length), data, 0, data.length);
//
// Update pointer in games file for this game index
//
barfInt(_idx, (long) ((gmidx + 1) * 4), ngofs + gmidx + 2);
//++ngidx;
ngofs += hdr.length + data.length + ngidx + 1;
//barfInt(_idx, (long) 0, ngidx);
barfInt(_idx, (long) (ngidx * 4), ngofs);
}
Update a Game in the database.
Note that this method is only required to work on Games that were
previously read from the same database as the update is being done. |