DroidFish: Time control information is now stored in pgn header data.

This commit is contained in:
Peter Osterlund
2013-04-14 13:33:36 +00:00
parent da625e085d
commit 26ac3b6ab5
7 changed files with 281 additions and 37 deletions

View File

@@ -53,6 +53,7 @@ import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO; import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.PgnToken; import org.petero.droidfish.gamelogic.PgnToken;
import org.petero.droidfish.gamelogic.GameTree.Node; import org.petero.droidfish.gamelogic.GameTree.Node;
import org.petero.droidfish.gamelogic.TimeControlData;
import org.petero.droidfish.gtb.Probe; import org.petero.droidfish.gtb.Probe;
import com.larvalabs.svgandroid.SVG; import com.larvalabs.svgandroid.SVG;
@@ -166,6 +167,7 @@ public class DroidFish extends Activity implements GUIInterface {
private int maxNumArrows; private int maxNumArrows;
private GameMode gameMode; private GameMode gameMode;
private boolean mPonderMode; private boolean mPonderMode;
private TimeControlData tcData = new TimeControlData();
private int mEngineThreads; private int mEngineThreads;
private String playerName; private String playerName;
private boolean boardFlipped; private boolean boardFlipped;
@@ -405,18 +407,21 @@ public class DroidFish extends Activity implements GUIInterface {
ctrl = new DroidChessController(this, gameTextListener, pgnOptions); ctrl = new DroidChessController(this, gameTextListener, pgnOptions);
egtbForceReload = true; egtbForceReload = true;
readPrefs(); readPrefs();
ctrl.newGame(gameMode); ctrl.newGame(gameMode, tcData);
{ {
byte[] data = null; byte[] data = null;
int version = 1;
if (savedInstanceState != null) { if (savedInstanceState != null) {
data = savedInstanceState.getByteArray("gameState"); data = savedInstanceState.getByteArray("gameState");
version = savedInstanceState.getInt("gameStateVersion", version);
} else { } else {
String dataStr = settings.getString("gameState", null); String dataStr = settings.getString("gameState", null);
version = settings.getInt("gameStateVersion", version);
if (dataStr != null) if (dataStr != null)
data = strToByteArr(dataStr); data = strToByteArr(dataStr);
} }
if (data != null) if (data != null)
ctrl.fromByteArray(data); ctrl.fromByteArray(data, version);
} }
ctrl.setGuiPaused(true); ctrl.setGuiPaused(true);
ctrl.setGuiPaused(false); ctrl.setGuiPaused(false);
@@ -813,6 +818,7 @@ public class DroidFish extends Activity implements GUIInterface {
if (ctrl != null) { if (ctrl != null) {
byte[] data = ctrl.toByteArray(); byte[] data = ctrl.toByteArray();
outState.putByteArray("gameState", data); outState.putByteArray("gameState", data);
outState.putInt("gameStateVersion", 2);
} }
} }
@@ -835,6 +841,7 @@ public class DroidFish extends Activity implements GUIInterface {
Editor editor = settings.edit(); Editor editor = settings.edit();
String dataStr = byteArrToString(data); String dataStr = byteArrToString(data);
editor.putString("gameState", dataStr); editor.putString("gameState", dataStr);
editor.putInt("gameStateVersion", 2);
editor.commit(); editor.commit();
} }
lastVisibleMillis = System.currentTimeMillis(); lastVisibleMillis = System.currentTimeMillis();
@@ -892,7 +899,7 @@ public class DroidFish extends Activity implements GUIInterface {
int timeControl = getIntSetting("timeControl", 120000); int timeControl = getIntSetting("timeControl", 120000);
int movesPerSession = getIntSetting("movesPerSession", 60); int movesPerSession = getIntSetting("movesPerSession", 60);
int timeIncrement = getIntSetting("timeIncrement", 0); int timeIncrement = getIntSetting("timeIncrement", 0);
ctrl.setTimeLimit(timeControl, movesPerSession, timeIncrement); tcData.setTimeControl(timeControl, movesPerSession, timeIncrement);
updateTimeControlTitle(); updateTimeControlTitle();
boardGestures = settings.getBoolean("boardGestures", true); boardGestures = settings.getBoolean("boardGestures", true);
@@ -1654,7 +1661,7 @@ public class DroidFish extends Activity implements GUIInterface {
gameMode = new GameMode(gameModeType); gameMode = new GameMode(gameModeType);
} }
// savePGNToFile(".autosave.pgn", true); // savePGNToFile(".autosave.pgn", true);
ctrl.newGame(gameMode); ctrl.newGame(gameMode, tcData);
ctrl.startGame(); ctrl.startGame();
setBoardFlip(true); setBoardFlip(true);
updateEngineTitle(); updateEngineTitle();

View File

@@ -34,7 +34,6 @@ import org.petero.droidfish.engine.DroidComputerPlayer.SearchRequest;
import org.petero.droidfish.engine.DroidComputerPlayer.SearchType; import org.petero.droidfish.engine.DroidComputerPlayer.SearchType;
import org.petero.droidfish.gamelogic.Game.GameState; import org.petero.droidfish.gamelogic.Game.GameState;
import org.petero.droidfish.gamelogic.GameTree.Node; import org.petero.droidfish.gamelogic.GameTree.Node;
import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField;
/** /**
* The glue between the chess engine and the GUI. * The glue between the chess engine and the GUI.
@@ -55,8 +54,6 @@ public class DroidChessController {
private int strength = 1000; private int strength = 1000;
private int numPV = 1; private int numPV = 1;
private TimeControlData tcData;
private SearchListener listener; private SearchListener listener;
private boolean guiPaused = false; private boolean guiPaused = false;
@@ -71,13 +68,12 @@ public class DroidChessController {
this.gameTextListener = gameTextListener; this.gameTextListener = gameTextListener;
gameMode = new GameMode(GameMode.TWO_PLAYERS); gameMode = new GameMode(GameMode.TWO_PLAYERS);
pgnOptions = options; pgnOptions = options;
tcData = new TimeControlData();
listener = new SearchListener(); listener = new SearchListener();
searchId = 0; searchId = 0;
} }
/** Start a new game. */ /** Start a new game. */
public final synchronized void newGame(GameMode gameMode) { public final synchronized void newGame(GameMode gameMode, TimeControlData tcData) {
boolean updateGui = abortSearch(); boolean updateGui = abortSearch();
if (updateGui) if (updateGui)
updateGUI(); updateGUI();
@@ -103,23 +99,11 @@ public class DroidChessController {
updateGameMode(); updateGameMode();
} }
/** Set time control parameters. */
public final synchronized void setTimeLimit(int time, int moves, int inc) {
tcData.setTimeControl(time, moves, inc);
if (game != null)
game.timeController.setTimeControl(tcData);
}
/** @return Array containing time control, moves per session and time increment. */ /** @return Array containing time control, moves per session and time increment. */
public final int[] getTimeLimit() { public final int[] getTimeLimit() {
if (game != null) if (game != null)
return game.timeController.getTimeLimit(game.currPos().whiteMove); return game.timeController.getTimeLimit(game.currPos().whiteMove);
int[] ret = new int[3]; return new int[]{5*60*1000, 60, 0};
TimeControlField tc = tcData.getTC(true).get(0);
ret[0] = (int)tc.timeControl;
ret[1] = tc.movesPerSession;
ret[2] = (int)tc.increment;
return ret;
} }
/** The chess clocks are stopped when the GUI is paused. */ /** The chess clocks are stopped when the GUI is paused. */
@@ -214,8 +198,8 @@ public class DroidChessController {
} }
/** De-serialize from byte array. */ /** De-serialize from byte array. */
public final synchronized void fromByteArray(byte[] data) { public final synchronized void fromByteArray(byte[] data, int version) {
game.fromByteArray(data); game.fromByteArray(data, version);
game.tree.translateMoves(); game.tree.translateMoves();
} }
@@ -236,7 +220,7 @@ public class DroidChessController {
/** Parse a string as FEN or PGN data. */ /** Parse a string as FEN or PGN data. */
public final synchronized void setFENOrPGN(String fenPgn) throws ChessParseError { public final synchronized void setFENOrPGN(String fenPgn) throws ChessParseError {
Game newGame = new Game(gameTextListener, tcData); Game newGame = new Game(gameTextListener, game.timeController.tcData);
try { try {
Position pos = TextIO.readFEN(fenPgn); Position pos = TextIO.readFEN(fenPgn);
newGame.setPos(pos); newGame.setPos(pos);

View File

@@ -40,16 +40,16 @@ public class Game {
public Game(PgnToken.PgnTokenReceiver gameTextListener, TimeControlData tcData) { public Game(PgnToken.PgnTokenReceiver gameTextListener, TimeControlData tcData) {
this.gameTextListener = gameTextListener; this.gameTextListener = gameTextListener;
tree = new GameTree(gameTextListener);
timeController = new TimeControl(); timeController = new TimeControl();
timeController.setTimeControl(tcData); timeController.setTimeControl(tcData);
gamePaused = false; gamePaused = false;
newGame(); newGame();
tree.setTimeControlData(tcData);
} }
/** De-serialize from byte array. */ /** De-serialize from byte array. */
final void fromByteArray(byte[] data) { final void fromByteArray(byte[] data, int version) {
tree.fromByteArray(data); tree.fromByteArray(data, version);
updateTimeControl(true); updateTimeControl(true);
} }
@@ -83,8 +83,12 @@ public class Game {
final boolean readPGN(String pgn, PGNOptions options) throws ChessParseError { final boolean readPGN(String pgn, PGNOptions options) throws ChessParseError {
boolean ret = tree.readPGN(pgn, options); boolean ret = tree.readPGN(pgn, options);
if (ret) if (ret) {
updateTimeControl(false); TimeControlData tcData = tree.getTimeControlData();
if (tcData != null)
timeController.setTimeControl(tcData);
updateTimeControl(tcData != null);
}
return ret; return ret;
} }

View File

@@ -28,11 +28,13 @@ import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.petero.droidfish.PGNOptions; import org.petero.droidfish.PGNOptions;
import org.petero.droidfish.gamelogic.Game.GameState; import org.petero.droidfish.gamelogic.Game.GameState;
import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField;
public class GameTree { public class GameTree {
// Data from the seven tag roster (STR) part of the PGN standard // Data from the seven tag roster (STR) part of the PGN standard
@@ -40,7 +42,7 @@ public class GameTree {
// Result is the last tag pair in the STR, but it is computed on demand from the game tree. // Result is the last tag pair in the STR, but it is computed on demand from the game tree.
Position startPos; Position startPos;
private String timeControl; private String timeControl, whiteTimeControl, blackTimeControl;
// Non-standard tags // Non-standard tags
static private final class TagPair { static private final class TagPair {
@@ -88,6 +90,8 @@ public class GameTree {
black = "?"; black = "?";
startPos = pos; startPos = pos;
timeControl = "?"; timeControl = "?";
whiteTimeControl = "?";
blackTimeControl = "?";
tagPairs = new ArrayList<TagPair>(); tagPairs = new ArrayList<TagPair>();
rootNode = new Node(); rootNode = new Node();
currentNode = rootNode; currentNode = rootNode;
@@ -308,6 +312,10 @@ public class GameTree {
} }
if (!timeControl.equals("?")) if (!timeControl.equals("?"))
addTagPair(out, "TimeControl", timeControl); addTagPair(out, "TimeControl", timeControl);
if (!whiteTimeControl.equals("?"))
addTagPair(out, "WhiteTimeControl", whiteTimeControl);
if (!blackTimeControl.equals("?"))
addTagPair(out, "BlackTimeControl", blackTimeControl);
// Write other non-standard tag pairs // Write other non-standard tag pairs
for (int i = 0; i < tagPairs.size(); i++) for (int i = 0; i < tagPairs.size(); i++)
@@ -559,6 +567,10 @@ public class GameTree {
result = val; result = val;
} else if (name.equals("TimeControl")) { } else if (name.equals("TimeControl")) {
timeControl = val; timeControl = val;
} else if (name.equals("WhiteTimeControl")) {
whiteTimeControl = val;
} else if (name.equals("BlackTimeControl")) {
blackTimeControl = val;
} else { } else {
this.tagPairs.add(tagPairs.get(i)); this.tagPairs.add(tagPairs.get(i));
} }
@@ -614,6 +626,8 @@ public class GameTree {
dos.writeUTF(black); dos.writeUTF(black);
dos.writeUTF(TextIO.toFEN(startPos)); dos.writeUTF(TextIO.toFEN(startPos));
dos.writeUTF(timeControl); dos.writeUTF(timeControl);
dos.writeUTF(whiteTimeControl);
dos.writeUTF(blackTimeControl);
int nTags = tagPairs.size(); int nTags = tagPairs.size();
dos.writeInt(nTags); dos.writeInt(nTags);
for (int i = 0; i < nTags; i++) { for (int i = 0; i < nTags; i++) {
@@ -637,7 +651,7 @@ public class GameTree {
} }
/** De-serialize from byte array. */ /** De-serialize from byte array. */
public final void fromByteArray(byte[] data) { public final void fromByteArray(byte[] data, int version) {
try { try {
ByteArrayInputStream bais = new ByteArrayInputStream(data); ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais); DataInputStream dis = new DataInputStream(bais);
@@ -650,6 +664,13 @@ public class GameTree {
startPos = TextIO.readFEN(dis.readUTF()); startPos = TextIO.readFEN(dis.readUTF());
currentPos = new Position(startPos); currentPos = new Position(startPos);
timeControl = dis.readUTF(); timeControl = dis.readUTF();
if (version >= 2) {
whiteTimeControl = dis.readUTF();
blackTimeControl = dis.readUTF();
} else {
whiteTimeControl = "?";
blackTimeControl = "?";
}
int nTags = dis.readInt(); int nTags = dis.readInt();
tagPairs.clear(); tagPairs.clear();
for (int i = 0; i < nTags; i++) { for (int i = 0; i < nTags; i++) {
@@ -1516,9 +1537,118 @@ public class GameTree {
headers.put("Round", round); headers.put("Round", round);
headers.put("White", white); headers.put("White", white);
headers.put("Black", black); headers.put("Black", black);
if (!timeControl.equals("?"))
headers.put("TimeControl", timeControl);
if (!whiteTimeControl.equals("?"))
headers.put("WhiteTimeControl", whiteTimeControl);
if (!blackTimeControl.equals("?"))
headers.put("BlackTimeControl", blackTimeControl);
for (int i = 0; i < tagPairs.size(); i++) { for (int i = 0; i < tagPairs.size(); i++) {
TagPair tp = tagPairs.get(i); TagPair tp = tagPairs.get(i);
headers.put(tp.tagName, tp.tagValue); headers.put(tp.tagName, tp.tagValue);
} }
} }
private ArrayList<TimeControlField> stringToTCFields(String tcStr) {
String[] fields = tcStr.split(":");
int nf = fields.length;
ArrayList<TimeControlField> ret = new ArrayList<TimeControlField>(nf);
for (int i = 0; i < nf; i++) {
String f = fields[i].trim();
if (f.equals("?") || f.equals("-") || f.contains("*")) {
// Not supported
} else {
try {
int moves = 0;
int time = 0;
int inc = 0;
int idx = f.indexOf('/');
if (idx > 0)
moves = Integer.parseInt(f.substring(0, idx).trim());
if (idx >= 0)
f = f.substring(idx+1);
idx = f.indexOf('+');
if (idx >= 0) {
if (idx > 0)
time = (int)(Double.parseDouble(f.substring(0, idx).trim())*1e3);
if (idx >= 0)
f = f.substring(idx+1);
inc = (int)(Double.parseDouble(f.trim())*1e3);
} else {
time = (int)(Double.parseDouble(f.trim())*1e3);
}
ret.add(new TimeControlField(time, moves, inc));
} catch (NumberFormatException ex) {
// Invalid syntax, ignore
}
}
}
return ret;
}
private String tcFieldsToString(ArrayList<TimeControlField> tcFields) {
StringBuilder sb = new StringBuilder();
int nf = tcFields.size();
for (int i = 0; i < nf; i++) {
if (i > 0)
sb.append(':');
TimeControlField t = tcFields.get(i);
if (t.movesPerSession > 0) {
sb.append(t.movesPerSession);
sb.append('/');
}
sb.append(t.timeControl / 1000);
int ms = (int)t.timeControl % 1000;
if (ms > 0) {
sb.append('.');
sb.append(String.format(Locale.US, "%03d", ms));
}
if (t.increment > 0) {
sb.append('+');
sb.append(t.increment / 1000);
ms = (int)t.increment % 1000;
if (ms > 0) {
sb.append('.');
sb.append(String.format(Locale.US, "%03d", ms));
}
}
}
return sb.toString();
}
/** Get time control data, or null if not present. */
public TimeControlData getTimeControlData() {
if (!whiteTimeControl.equals("?") && !blackTimeControl.equals("?")) {
ArrayList<TimeControlField> tcW = stringToTCFields(whiteTimeControl);
ArrayList<TimeControlField> tcB = stringToTCFields(blackTimeControl);
if (!tcW.isEmpty() && !tcB.isEmpty()) {
TimeControlData tcData = new TimeControlData();
tcData.tcW = tcW;
tcData.tcB = tcB;
return tcData;
}
}
if (!timeControl.equals("?")) {
ArrayList<TimeControlField> tc = stringToTCFields(timeControl);
if (!tc.isEmpty()) {
TimeControlData tcData = new TimeControlData();
tcData.tcW = tc;
tcData.tcB = tc;
return tcData;
}
}
return null;
}
public void setTimeControlData(TimeControlData tcData) {
if (tcData.isSymmetric()) {
timeControl = tcFieldsToString(tcData.tcW);
whiteTimeControl = "?";
blackTimeControl = "?";
} else {
whiteTimeControl = tcFieldsToString(tcData.tcW);
blackTimeControl = tcFieldsToString(tcData.tcB);
timeControl = "?";
}
}
} }

View File

@@ -24,7 +24,7 @@ import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField;
/** Keep track of time control information for both players. */ /** Keep track of time control information for both players. */
public class TimeControl { public class TimeControl {
private TimeControlData tcData; TimeControlData tcData;
private long whiteBaseTime; // Current remaining time, or remaining time when clock started private long whiteBaseTime; // Current remaining time, or remaining time when clock started
private long blackBaseTime; // Current remaining time, or remaining time when clock started private long blackBaseTime; // Current remaining time, or remaining time when clock started

View File

@@ -4,9 +4,9 @@ import java.util.ArrayList;
public final class TimeControlData { public final class TimeControlData {
public static final class TimeControlField { public static final class TimeControlField {
long timeControl; long timeControl; // Time in milliseconds
int movesPerSession; int movesPerSession;
long increment; long increment; // Increment in milliseconds
public TimeControlField(long time, int moves, long inc) { public TimeControlField(long time, int moves, long inc) {
timeControl = time; timeControl = time;
@@ -17,13 +17,15 @@ public final class TimeControlData {
ArrayList<TimeControlField> tcW, tcB; ArrayList<TimeControlField> tcW, tcB;
TimeControlData() { /** Constructor. Set a default time control. */
public TimeControlData() {
tcW = new ArrayList<TimeControlField>(); tcW = new ArrayList<TimeControlField>();
tcW.add(new TimeControlField(5*60*1000, 60, 0)); tcW.add(new TimeControlField(5*60*1000, 60, 0));
tcB = new ArrayList<TimeControlField>(); tcB = new ArrayList<TimeControlField>();
tcB.add(new TimeControlField(5*60*1000, 60, 0)); tcB.add(new TimeControlField(5*60*1000, 60, 0));
} }
/** Set a single time control for both white and black. */
public final void setTimeControl(long time, int moves, long inc) { public final void setTimeControl(long time, int moves, long inc) {
tcW = new ArrayList<TimeControlField>(); tcW = new ArrayList<TimeControlField>();
tcW.add(new TimeControlField(time, moves, inc)); tcW.add(new TimeControlField(time, moves, inc));
@@ -35,4 +37,32 @@ public final class TimeControlData {
public ArrayList<TimeControlField> getTC(boolean whiteMove) { public ArrayList<TimeControlField> getTC(boolean whiteMove) {
return whiteMove ? tcW : tcB; return whiteMove ? tcW : tcB;
} }
/** Return true if white and black time controls are equal. */
public boolean isSymmetric() {
return arrayEquals(tcW, tcB);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TimeControlData))
return false;
TimeControlData tc2 = (TimeControlData)o;
return arrayEquals(tcW, tc2.tcW) && arrayEquals(tcB, tc2.tcB);
}
private static boolean arrayEquals(ArrayList<TimeControlField> a1,
ArrayList<TimeControlField> a2) {
if (a1.size() != a2.size())
return false;
for (int i = 0; i < a1.size(); i++) {
TimeControlField f1 = a1.get(i);
TimeControlField f2 = a2.get(i);
if ((f1.timeControl != f2.timeControl) ||
(f1.movesPerSession != f2.movesPerSession) ||
(f1.increment != f2.increment))
return false;
}
return true;
}
} }

View File

@@ -21,6 +21,8 @@ package org.petero.droidfish.gamelogic;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -28,6 +30,7 @@ import org.petero.droidfish.PGNOptions;
import org.petero.droidfish.gamelogic.Game.GameState; import org.petero.droidfish.gamelogic.Game.GameState;
import org.petero.droidfish.gamelogic.GameTree.Node; import org.petero.droidfish.gamelogic.GameTree.Node;
import org.petero.droidfish.gamelogic.GameTree.PgnScanner; import org.petero.droidfish.gamelogic.GameTree.PgnScanner;
import org.petero.droidfish.gamelogic.TimeControlData.TimeControlField;
public class GameTreeTest extends TestCase { public class GameTreeTest extends TestCase {
@@ -96,7 +99,7 @@ public class GameTreeTest extends TestCase {
byte[] serialState = gt.toByteArray(); byte[] serialState = gt.toByteArray();
gt = new GameTree(null); gt = new GameTree(null);
gt.fromByteArray(serialState); gt.fromByteArray(serialState, 2);
assertEquals(expectedPos, gt.currentPos); assertEquals(expectedPos, gt.currentPos);
gt.goBack(); gt.goBack();
@@ -694,4 +697,90 @@ public class GameTreeTest extends TestCase {
gt.goNode(ne4); gt.goNode(ne4);
assertEquals("e4* e5 Nf3 Nc6 Bb5 a6", getMoveListAsString(gt)); assertEquals("e4* e5 Nf3 Nc6 Bb5 a6", getMoveListAsString(gt));
} }
public final void testTimeControl() throws ChessParseError {
GameTree gt = new GameTree(null);
PGNOptions options = new PGNOptions();
TimeControlData tcData = new TimeControlData();
tcData.setTimeControl(180*1000, 35, 0);
gt.setTimeControlData(tcData);
TimeControlData tcData2 = gt.getTimeControlData();
assertTrue(tcData2.isSymmetric());
assertTrue(tcData.equals(tcData2));
Map<String,String> headers = new TreeMap<String,String>();
gt.getHeaders(headers);
assertEquals("35/180", headers.get("TimeControl"));
assertEquals(null, headers.get("WhiteTimeControl"));
assertEquals(null, headers.get("BlackTimeControl"));
String pgn = gt.toPGN(options);
boolean res = gt.readPGN(pgn, options);
assertTrue(res);
tcData2 = gt.getTimeControlData();
assertTrue(tcData2.isSymmetric());
assertEquals(tcData, tcData2);
headers = new TreeMap<String,String>();
gt.getHeaders(headers);
assertEquals("35/180", headers.get("TimeControl"));
assertEquals(null, headers.get("WhiteTimeControl"));
assertEquals(null, headers.get("BlackTimeControl"));
tcData = new TimeControlData();
tcData.tcW.clear();
tcData.tcW.add(new TimeControlField(15*60*1000,40,0));
tcData.tcW.add(new TimeControlField(5*60*1000+345,20,0));
tcData.tcW.add(new TimeControlField(0,10,1000));
tcData.tcW.add(new TimeControlField(0,0,5000));
tcData.tcB.clear();
tcData.tcB.add(new TimeControlField(60*1000,20,3004));
tcData.tcB.add(new TimeControlField(30*1000,0,0));
gt.setTimeControlData(tcData);
headers = new TreeMap<String,String>();
gt.getHeaders(headers);
assertEquals(null, headers.get("TimeControl"));
assertEquals("40/900:20/300.345:10/0+1:0+5", headers.get("WhiteTimeControl"));
assertEquals("20/60+3.004:30", headers.get("BlackTimeControl"));
tcData2 = gt.getTimeControlData();
assertTrue(!tcData2.isSymmetric());
assertEquals(tcData, tcData2);
pgn = gt.toPGN(options);
res = gt.readPGN(pgn, options);
assertTrue(res);
tcData2 = gt.getTimeControlData();
assertTrue(!tcData2.isSymmetric());
assertEquals(tcData, tcData2);
headers = new TreeMap<String,String>();
gt.getHeaders(headers);
assertEquals(null, headers.get("TimeControl"));
assertEquals("40/900:20/300.345:10/0+1:0+5", headers.get("WhiteTimeControl"));
assertEquals("20/60+3.004:30", headers.get("BlackTimeControl"));
tcData = new TimeControlData();
tcData.setTimeControl(2*60*1000, 0, 12000);
gt.setTimeControlData(tcData);
headers = new TreeMap<String,String>();
gt.getHeaders(headers);
assertEquals("120+12", headers.get("TimeControl"));
assertEquals(null, headers.get("WhiteTimeControl"));
assertEquals(null, headers.get("BlackTimeControl"));
// Test pgn data with extra white space
res = gt.readPGN("[TimeControl \" 40 / 5400 + 60 : 3.14 + 2.718 \"]", options);
assertTrue(res);
tcData = gt.getTimeControlData();
assertTrue(tcData.isSymmetric());
assertEquals(2, tcData.tcW.size());
TimeControlField tf = tcData.tcW.get(0);
assertEquals(40, tf.movesPerSession);
assertEquals(5400*1000, tf.timeControl);
assertEquals(60*1000, tf.increment);
tf = tcData.tcW.get(1);
assertEquals(0, tf.movesPerSession);
assertEquals(3140, tf.timeControl);
assertEquals(2718, tf.increment);
}
} }