From ba7d8f61eb74dff6191440dfab4c43864242c9bd Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Sat, 13 Apr 2013 21:25:25 +0000 Subject: [PATCH] DroidFish: Added support in TimeControl class for multiple time controls and for different time controls for white and black. --- .../droidfish/engine/DroidComputerPlayer.java | 16 ++- .../gamelogic/DroidChessController.java | 13 +-- .../org/petero/droidfish/gamelogic/Game.java | 5 +- .../petero/droidfish/gamelogic/GameTree.java | 2 +- .../droidfish/gamelogic/TimeControl.java | 105 ++++++++++++----- .../droidfish/gamelogic/TimeControlTest.java | 108 ++++++++++++++++-- 6 files changed, 191 insertions(+), 58 deletions(-) diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java index 7defc1e..0818352 100644 --- a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java +++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java @@ -109,7 +109,8 @@ public class DroidComputerPlayer { boolean isAnalyze; // True if analysis search int wTime; // White remaining time, milliseconds int bTime; // Black remaining time, milliseconds - int inc; // Time increment per move, milliseconds + int wInc; // White time increment per move, milliseconds + int bInc; // Black time increment per move, milliseconds int movesToGo; // Number of moves to next time control String engine; // Engine name (identifier) @@ -157,7 +158,7 @@ public class DroidComputerPlayer { public static SearchRequest searchRequest(int id, long now, Position prevPos, ArrayList mList, Position currPos, boolean drawOffer, - int wTime, int bTime, int inc, int movesToGo, + int wTime, int bTime, int wInc, int bInc, int movesToGo, boolean ponderEnabled, Move ponderMove, String engine, int engineThreads, int strength) { @@ -172,7 +173,8 @@ public class DroidComputerPlayer { sr.isAnalyze = false; sr.wTime = wTime; sr.bTime = bTime; - sr.inc = inc; + sr.wInc = wInc; + sr.bInc = bInc; sr.movesToGo = movesToGo; sr.engine = engine; sr.engineThreads = engineThreads; @@ -212,7 +214,7 @@ public class DroidComputerPlayer { sr.drawOffer = drawOffer; sr.isSearch = false; sr.isAnalyze = true; - sr.wTime = sr.bTime = sr.inc = sr.movesToGo = 0; + sr.wTime = sr.bTime = sr.wInc = sr.bInc = sr.movesToGo = 0; sr.engine = engine; sr.engineThreads = engineThreads; sr.strength = 1000; @@ -554,8 +556,10 @@ public class DroidComputerPlayer { if (sr.bTime < 1) sr.bTime = 1; StringBuilder goStr = new StringBuilder(96); goStr.append(String.format("go wtime %d btime %d", sr.wTime, sr.bTime)); - if (sr.inc > 0) - goStr.append(String.format(" winc %d binc %d", sr.inc, sr.inc)); + if (sr.wInc > 0) + goStr.append(String.format(" winc %d", sr.wInc)); + if (sr.bInc > 0) + goStr.append(String.format(" binc %d", sr.bInc)); if (sr.movesToGo > 0) goStr.append(String.format(" movestogo %d", sr.movesToGo)); if (sr.ponderMove != null) diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java index 24917dc..fa4eb0f 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java @@ -873,18 +873,15 @@ public class DroidChessController { long now = System.currentTimeMillis(); int wTime = game.timeController.getRemainingTime(true, now); int bTime = game.timeController.getRemainingTime(false, now); - int inc = game.timeController.getIncrement(); - int movesToGo = game.timeController.getMovesToTC(); - if (ponder && !currPos.whiteMove && (movesToGo > 0)) { - movesToGo--; - if (movesToGo <= 0) - movesToGo += game.timeController.getMovesPerSession(); - } + int wInc = game.timeController.getIncrement(true); + int bInc = game.timeController.getIncrement(false); + boolean wtm = currPos.whiteMove; + int movesToGo = game.timeController.getMovesToTC(wtm); final Move fPonderMove = ponder ? ponderMove : null; SearchRequest sr = DroidComputerPlayer.SearchRequest.searchRequest( searchId, now, ph.first, ph.second, currPos, game.haveDrawOffer(), - wTime, bTime, inc, movesToGo, + wTime, bTime, wInc, bInc, movesToGo, gui.ponderMode(), fPonderMove, engine, gui.engineThreads(), strength); diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java index 9c961ff..4be0be2 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java @@ -201,9 +201,8 @@ public class Game { int move = currPos.fullMoveCounter; boolean wtm = currPos.whiteMove; if (discardElapsed || (move != timeController.currentMove) || (wtm != timeController.whiteToMove)) { - int initialTime = timeController.getInitialTime(); - int whiteBaseTime = tree.getRemainingTime(true, initialTime); - int blackBaseTime = tree.getRemainingTime(false, initialTime); + int whiteBaseTime = tree.getRemainingTime(true, timeController.getInitialTime(true)); + int blackBaseTime = tree.getRemainingTime(false, timeController.getInitialTime(false)); timeController.setCurrentMove(move, wtm, whiteBaseTime, blackBaseTime); } long now = System.currentTimeMillis(); diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java index e6839ac..aed0b51 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java @@ -826,7 +826,7 @@ public class GameTree { } final int getRemainingTime(boolean whiteMove, int initialTime) { - int undef = Integer.MIN_VALUE; + final int undef = Integer.MIN_VALUE; int remainingTime = undef; Node node = currentNode; boolean wtm = currentPos.whiteMove; diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java b/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java index 89fe393..e745334 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java @@ -18,19 +18,33 @@ package org.petero.droidfish.gamelogic; -public class TimeControl { - private long timeControl; - private int movesPerSession; - private long increment; +import java.util.ArrayList; - private long whiteBaseTime; - private long blackBaseTime; +/** Keep track of time control information for both players. */ +public class TimeControl { + public static final class TimeControlField { + long timeControl; + int movesPerSession; + long increment; + + public TimeControlField(long time, int moves, long inc) { + timeControl = time; + movesPerSession = moves; + increment = inc; + } + } + + private ArrayList tcW; + private ArrayList tcB; + + private long whiteBaseTime; // Current remaining time, or remaining time when clock started + private long blackBaseTime; // Current remaining time, or remaining time when clock started int currentMove; boolean whiteToMove; - long elapsed; // Accumulated elapsed time for this move. - long timerT0; // Time when timer started. 0 if timer is stopped. + private long elapsed; // Accumulated elapsed time for this move. + private long timerT0; // Time when timer started. 0 if timer is stopped. /** Constructor. Sets time control to "game in 5min". */ @@ -48,9 +62,17 @@ public class TimeControl { /** Set time control to "moves" moves in "time" milliseconds, + inc milliseconds per move. */ public final void setTimeControl(long time, int moves, long inc) { - timeControl = time; - movesPerSession = moves; - increment = inc; + tcW = new ArrayList(); + tcW.add(new TimeControlField(time, moves, inc)); + tcB = new ArrayList(); + tcB.add(new TimeControlField(time, moves, inc)); + } + + /** Set time controls for white and black players. */ + public final void setTimeControl(ArrayList whiteTC, + ArrayList blackTC) { + tcW = whiteTC; + tcB = blackTC; } public final void setCurrentMove(int move, boolean whiteToMove, long whiteBaseTime, long blackBaseTime) { @@ -86,11 +108,20 @@ public class TimeControl { /** Compute new remaining time after a move is made. */ public final int moveMade(long now, boolean useIncrement) { stopTimer(now); + + ArrayList tc = whiteToMove ? tcW : tcB; + Pair tcInfo = getCurrentTC(whiteToMove); + int tcIdx = tcInfo.first; + int movesToTc = tcInfo.second; + long remaining = getRemainingTime(whiteToMove, now); if (useIncrement) { - remaining += increment; - if (getMovesToTC() == 1) - remaining += timeControl; + remaining += tc.get(tcIdx).increment; + if (movesToTc == 1) { + if (tcIdx+1 < tc.size()) + tcIdx++; + remaining += tc.get(tcIdx).timeControl; + } } elapsed = 0; return (int)remaining; @@ -108,24 +139,42 @@ public class TimeControl { return (int)remaining; } - public final int getInitialTime() { - return (int)timeControl; + /** Get initial thinking time in milliseconds. */ + public final int getInitialTime(boolean whiteMove) { + ArrayList tc = whiteMove ? tcW : tcB; + return (int)tc.get(0).timeControl; } - public final int getIncrement() { - return (int)increment; + /** Get time increment in milliseconds after playing next move. */ + public final int getIncrement(boolean whiteMove) { + ArrayList tc = whiteMove ? tcW : tcB; + int tcIdx = getCurrentTC(whiteMove).first; + return (int)tc.get(tcIdx).increment; } - public final int getMovesToTC() { - if (movesPerSession <= 0) - return 0; + /** Return number of moves to the next time control, or 0 if "sudden death". */ + public final int getMovesToTC(boolean whiteMove) { + return getCurrentTC(whiteMove).second; + } + + /** Return the current active time control index and number of moves to next time control. */ + private Pair getCurrentTC(boolean whiteMove) { + ArrayList tc = whiteMove ? tcW : tcB; + int tcIdx = 0; + final int lastTcIdx = tc.size() - 1; int nextTC = 1; - while (nextTC <= currentMove) - nextTC += movesPerSession; - return nextTC - currentMove; - } - - public final int getMovesPerSession() { - return movesPerSession; + int currMove = currentMove; + if (!whiteToMove && whiteMove) + currMove++; + while (true) { + if (tc.get(tcIdx).movesPerSession <= 0) + return new Pair(tcIdx, 0); + nextTC += tc.get(tcIdx).movesPerSession; + if (nextTC > currMove) + break; + if (tcIdx < lastTcIdx) + tcIdx++; + } + return new Pair(tcIdx, nextTC - currMove); } } diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java index fab2c24..a0651c4 100644 --- a/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/TimeControlTest.java @@ -18,6 +18,8 @@ package org.petero.droidfish.gamelogic; +import java.util.ArrayList; + import junit.framework.TestCase; @@ -31,20 +33,24 @@ public class TimeControlTest extends TestCase { long t0 = 1000; tc.setTimeControl(totTime, 0, 0); tc.setCurrentMove(1, true, totTime, totTime); - assertEquals(0, tc.getMovesToTC()); - assertEquals(0, tc.getIncrement()); + assertEquals(0, tc.getMovesToTC(true)); + assertEquals(0, tc.getMovesToTC(false)); + assertEquals(0, tc.getIncrement(true)); + assertEquals(0, tc.getIncrement(false)); assertEquals(totTime, tc.getRemainingTime(true, 0)); tc.startTimer(t0); int remain = tc.moveMade(t0 + 1000, true); assertEquals(totTime - 1000, remain); tc.setCurrentMove(2, true, totTime - 1000, totTime); - assertEquals(0, tc.getMovesToTC()); + assertEquals(0, tc.getMovesToTC(true)); + assertEquals(0, tc.getMovesToTC(false)); assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 4711)); assertEquals(totTime, tc.getRemainingTime(false, t0 + 4711)); tc.setCurrentMove(1, false, totTime - 1000, totTime); - assertEquals(0, tc.getMovesToTC()); + assertEquals(0, tc.getMovesToTC(true)); + assertEquals(0, tc.getMovesToTC(false)); assertEquals(totTime - 1000, tc.getRemainingTime(true, t0 + 4711)); assertEquals(totTime, tc.getRemainingTime(false, t0 + 4711)); @@ -66,24 +72,102 @@ public class TimeControlTest extends TestCase { TimeControl tc = new TimeControl(); tc.setTimeControl(2 * 60 * 1000, 40, 0); tc.setCurrentMove(1, true, 0, 0); - assertEquals(40, tc.getMovesToTC()); + assertEquals(40, tc.getMovesToTC(true)); + assertEquals(40, tc.getMovesToTC(false)); tc.setCurrentMove(1, false, 0, 0); - assertEquals(40, tc.getMovesToTC()); + assertEquals(39, tc.getMovesToTC(true)); + assertEquals(40, tc.getMovesToTC(false)); tc.setCurrentMove(2, true, 0, 0); - assertEquals(39, tc.getMovesToTC()); + assertEquals(39, tc.getMovesToTC(true)); + assertEquals(39, tc.getMovesToTC(false)); tc.setCurrentMove(40, true, 0, 0); - assertEquals(1, tc.getMovesToTC()); + assertEquals(1, tc.getMovesToTC(true)); + assertEquals(1, tc.getMovesToTC(false)); + + tc.setCurrentMove(40, false, 0, 0); + assertEquals(40, tc.getMovesToTC(true)); + assertEquals(1, tc.getMovesToTC(false)); tc.setCurrentMove(41, true, 0, 0); - assertEquals(40, tc.getMovesToTC()); + assertEquals(40, tc.getMovesToTC(true)); + assertEquals(40, tc.getMovesToTC(false)); tc.setCurrentMove(80, true, 0, 0); - assertEquals(1, tc.getMovesToTC()); + assertEquals(1, tc.getMovesToTC(true)); + assertEquals(1, tc.getMovesToTC(false)); + + tc.setCurrentMove(80, false, 0, 0); + assertEquals(40, tc.getMovesToTC(true)); + assertEquals(1, tc.getMovesToTC(false)); tc.setCurrentMove(81, true, 0, 0); - assertEquals(40, tc.getMovesToTC()); + assertEquals(40, tc.getMovesToTC(true)); + assertEquals(40, tc.getMovesToTC(false)); + } + + private TimeControl.TimeControlField tcf(long time, int moves, long inc) { + return new TimeControl.TimeControlField(time, moves, inc); + } + + /** Test multiple time controls. */ + public void testMultiTimeControl() { + TimeControl tc = new TimeControl(); + ArrayList tcW = new ArrayList(); + tcW.add(tcf(120*60*1000, 40, 0)); + tcW.add(tcf(60*60*1000, 20, 0)); + tcW.add(tcf(30*60*1000, 0, 15*1000)); + ArrayList tcB = new ArrayList(); + tcB.add(tcf(5*60*1000, 60, 1000)); + tc.setTimeControl(tcW, tcB); + + assertEquals(40, tc.getMovesToTC(true)); + assertEquals(60, tc.getMovesToTC(false)); + assertEquals(0, tc.getIncrement(true)); + assertEquals(1000, tc.getIncrement(false)); + + tc.setCurrentMove(40, true, 0, 0); + assertEquals(1, tc.getMovesToTC(true)); + assertEquals(21, tc.getMovesToTC(false)); + assertEquals(0, tc.getIncrement(true)); + assertEquals(1000, tc.getIncrement(false)); + + tc.setCurrentMove(40, false, 0, 0); + assertEquals(20, tc.getMovesToTC(true)); + assertEquals(21, tc.getMovesToTC(false)); + assertEquals(0, tc.getIncrement(true)); + assertEquals(1000, tc.getIncrement(false)); + + tc.setCurrentMove(60, true, 0, 0); + assertEquals(1, tc.getMovesToTC(true)); + assertEquals(1, tc.getMovesToTC(false)); + assertEquals(0, tc.getIncrement(true)); + assertEquals(1000, tc.getIncrement(false)); + + tc.setCurrentMove(61, true, 0, 0); + assertEquals(0, tc.getMovesToTC(true)); + assertEquals(60, tc.getMovesToTC(false)); + assertEquals(15000, tc.getIncrement(true)); + assertEquals(1000, tc.getIncrement(false)); + + + long wBaseTime = 60*1000; + long bBaseTime = 50*1000; + tc.setCurrentMove(30, true, wBaseTime, bBaseTime); + tc.startTimer(1500); + wBaseTime = tc.moveMade(1500 + 3000, true); + assertEquals(60*1000-3000, wBaseTime); + tc.setCurrentMove(30, false, wBaseTime, bBaseTime); + assertEquals(60*1000-3000, tc.getRemainingTime(true, 1500 + 3000)); + assertEquals(50*1000, tc.getRemainingTime(false, 1500 + 3000)); + + tc.startTimer(5000); + bBaseTime = tc.moveMade(9000, true); + assertEquals(50000 - 4000 + 1000, bBaseTime); + tc.setCurrentMove(31, true, wBaseTime, bBaseTime); + assertEquals(60*1000-3000, tc.getRemainingTime(true, 9000)); + assertEquals(50000 - 4000 + 1000, tc.getRemainingTime(false, 9000)); } public void testExtraTime() { @@ -116,7 +200,7 @@ public class TimeControlTest extends TestCase { assertEquals(timeCont - 1000 + timeCont + inc - 3000 + inc, tc.getRemainingTime(true, t0 + 4711)); assertEquals(timeCont - 4000 + timeCont + inc, tc.getRemainingTime(false, t0 + 4711)); - // No increment when move made int paused mode, ie analysis mode + // No increment when move made in paused mode, ie analysis mode tc.startTimer(t0 + 9000); bBaseTime = tc.moveMade(t0 + 10000, false); tc.setCurrentMove(7, true, wBaseTime, bBaseTime);