test(protocol): add uci, clock, and time-control tests
This commit is contained in:
1 parent
5f2c484603
commit
ec8be64f82
3 files changed
+147
No files matched your search
@@ -0,0 +1,47 @@
|
||||
package gambit
|
||||
|
||||
class TimeControlTest extends munit.FunSuite:
|
||||
import TimeControl.Limits
|
||||
|
||||
test("movetime is honored minus a safety overhead") {
|
||||
assertEquals(TimeControl.budgetMs(Limits(movetime = 200L), true), 175L)
|
||||
}
|
||||
|
||||
test("infinite and depth-only searches carry no time budget") {
|
||||
assertEquals(TimeControl.budgetMs(Limits(infinite = true), true), 0L)
|
||||
assertEquals(TimeControl.budgetMs(Limits(depth = 12), true), 0L)
|
||||
assertEquals(TimeControl.budgetMs(Limits(), true), 0L)
|
||||
}
|
||||
|
||||
test("a fixed number of moves to go splits the clock evenly") {
|
||||
assertEquals(TimeControl.budgetMs(Limits(wtime = 60000L, btime = 60000L, movestogo = 30), true), 1975L)
|
||||
}
|
||||
|
||||
test("increment is folded into the per-move budget") {
|
||||
assertEquals(TimeControl.budgetMs(Limits(wtime = 1000L, winc = 1000L), true), 758L)
|
||||
}
|
||||
|
||||
test("the side to move selects its own clock") {
|
||||
val limits = Limits(wtime = 60000L, btime = 5000L, movestogo = 10)
|
||||
assertEquals(TimeControl.budgetMs(limits, true), 5975L)
|
||||
assertEquals(TimeControl.budgetMs(limits, false), 475L)
|
||||
}
|
||||
|
||||
test("very low time never returns a non-positive budget") {
|
||||
assert(TimeControl.budgetMs(Limits(wtime = 40L), true) >= 1L)
|
||||
assert(TimeControl.budgetMs(Limits(wtime = 0L, winc = 10L), true) >= 1L)
|
||||
}
|
||||
|
||||
test("plan couples a depth cap with the time budget") {
|
||||
assertEquals(TimeControl.plan(Limits(depth = 8), true), TimeControl.Plan(8, 0L))
|
||||
assertEquals(TimeControl.plan(Limits(movetime = 1000L), true), TimeControl.Plan(64, 975L))
|
||||
}
|
||||
|
||||
test("parse reads every clock token and ignores unknown ones") {
|
||||
val limits = TimeControl.parse("wtime 300000 btime 250000 winc 2000 binc 1500 movestogo 40 ponder".split(" ").toList)
|
||||
assertEquals(limits.wtime, 300000L)
|
||||
assertEquals(limits.btime, 250000L)
|
||||
assertEquals(limits.winc, 2000L)
|
||||
assertEquals(limits.binc, 1500L)
|
||||
assertEquals(limits.movestogo, 40)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package gambit
|
||||
|
||||
class UciTest extends munit.FunSuite:
|
||||
|
||||
test("uci handshake replies with id and uciok") {
|
||||
val out = new Uci().handle("uci")
|
||||
assertEquals(out, List("id name gambit", "id author gambit", "uciok"))
|
||||
}
|
||||
|
||||
test("isready replies readyok") {
|
||||
assertEquals(new Uci().handle("isready"), List("readyok"))
|
||||
}
|
||||
|
||||
test("position startpos with moves then go returns a legal bestmove") {
|
||||
val uci = new Uci()
|
||||
uci.handle("ucinewgame")
|
||||
uci.handle("position startpos moves e2e4 e7e5 g1f3")
|
||||
val out = uci.handle("go depth 4")
|
||||
val best = out.last
|
||||
assert(best.startsWith("bestmove "), s"got $best")
|
||||
val move = best.stripPrefix("bestmove ")
|
||||
assert(move.matches("[a-h][1-8][a-h][1-8][nbrq]?"), s"malformed move $move")
|
||||
assert(move != "0000", "expected a real move")
|
||||
}
|
||||
|
||||
test("position fen with trailing moves is parsed and searched") {
|
||||
val uci = new Uci()
|
||||
uci.handle("position fen 4k3/8/8/8/8/8/3q4/4K1R1 w - - 0 1 moves g1g2")
|
||||
val res = uci.handle("go depth 4")
|
||||
assert(res.last.startsWith("bestmove "), res.mkString(" | "))
|
||||
}
|
||||
|
||||
test("go reports mate for a forced mate in one") {
|
||||
val uci = new Uci()
|
||||
uci.handle("position fen 6k1/5ppp/8/8/8/8/8/R6K w - - 0 1")
|
||||
val out = uci.handle("go depth 3")
|
||||
assert(out.exists(_.contains("score mate 1")), out.mkString(" | "))
|
||||
assertEquals(out.last, "bestmove a1a8")
|
||||
}
|
||||
|
||||
test("go reports mate in two and the mating first move") {
|
||||
val uci = new Uci()
|
||||
uci.handle("position fen 7k/R7/5K2/8/8/8/8/8 w - - 0 1")
|
||||
val out = uci.handle("go depth 5")
|
||||
assert(out.exists(_.contains("score mate 2")), out.mkString(" | "))
|
||||
assertEquals(out.last, "bestmove f6g6")
|
||||
}
|
||||
|
||||
test("movetime search still returns a bestmove") {
|
||||
val uci = new Uci()
|
||||
uci.handle("position startpos")
|
||||
val out = uci.handle("go movetime 200")
|
||||
assert(out.last.startsWith("bestmove "))
|
||||
assert(out.nonEmpty)
|
||||
}
|
||||
|
||||
test("run consumes a command stream up to quit") {
|
||||
val collected = scala.collection.mutable.ListBuffer[String]()
|
||||
val input = List("uci", "isready", "quit", "go depth 3").iterator
|
||||
new Uci().run(input, collected += _)
|
||||
assert(collected.contains("uciok"))
|
||||
assert(collected.contains("readyok"))
|
||||
assert(!collected.exists(_.startsWith("bestmove")), "commands after quit must be ignored")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package gambit
|
||||
|
||||
class UciClockTest extends munit.FunSuite:
|
||||
|
||||
test("go with a wtime/btime clock returns a legal bestmove") {
|
||||
val uci = new Uci()
|
||||
uci.handle("position startpos")
|
||||
val out = uci.handle("go wtime 3000 btime 3000 movestogo 20")
|
||||
val best = out.last
|
||||
assert(best.startsWith("bestmove "), out.mkString(" | "))
|
||||
val move = best.stripPrefix("bestmove ")
|
||||
assert(move.matches("[a-h][1-8][a-h][1-8][nbrq]?"), s"malformed move $move")
|
||||
assert(out.exists(_.startsWith("info depth ")), "clock search must still stream info")
|
||||
}
|
||||
|
||||
test("go with an increment-only sudden-death clock still moves") {
|
||||
val uci = new Uci()
|
||||
uci.handle("position startpos moves e2e4 c7c5")
|
||||
val out = uci.handle("go wtime 1500 btime 1500 winc 100 binc 100")
|
||||
assert(out.last.startsWith("bestmove "))
|
||||
assert(out.last.stripPrefix("bestmove ") != "0000")
|
||||
}
|
||||
|
||||
test("stop over the streaming loop aborts an infinite search and prints a bestmove") {
|
||||
val collected = scala.collection.mutable.ListBuffer[String]()
|
||||
val script = List[() => String](
|
||||
() => "position startpos",
|
||||
() => "go infinite",
|
||||
() => { Thread.sleep(200); "stop" },
|
||||
() => "quit"
|
||||
)
|
||||
val input = script.iterator.map(_())
|
||||
new Uci().run(input, collected += _)
|
||||
assert(collected.exists(_.startsWith("bestmove ")), collected.mkString(" | "))
|
||||
assert(collected.exists(_.startsWith("info depth ")), "an infinite search should report progress")
|
||||
}
|
||||
Reference in new issue
Block a user