diff --git a/Main.scala b/Main.scala new file mode 100644 index 0000000..6edfb80 --- /dev/null +++ b/Main.scala @@ -0,0 +1,60 @@ +package gambit + +@main def gambit(args: String*): Unit = + args.toList match + case Nil => runDemo() + case "uci" :: _ => new Uci().run(scala.io.Source.stdin.getLines(), println) + case "selfplay" :: opts => runSelfPlay(opts) + case "epd" :: opts => runEpd(opts) + case other => runPerft(other) + +private def runEpd(opts: List[String]): Unit = + val timeMs = opts.headOption.map(_.toLong).getOrElse(500L) + println(s"EPD test-suite runner: ${WinAtChess.positions.length} positions, ${timeMs}ms each") + val t0 = System.nanoTime() + val results = Epd.runSuite(WinAtChess.positions, timeMs) + val ms = (System.nanoTime() - t0) / 1000000 + print(Epd.report(results)) + println(s"total wall time ${ms} ms") + +private def runDemo(): Unit = + println("gambit chess engine") + println("usage:") + println(" scala-cli run gambit -- uci") + println(" scala-cli run gambit -- selfplay [maxPlies] [depth]") + println(" scala-cli run gambit -- ") + println() + runPerft(List("startpos", "4")) + +private def runPerft(args: List[String]): Unit = + val depth = args.last.toInt + val fenArg = args.dropRight(1).mkString(" ") + val fen = if fenArg.isEmpty || fenArg == "startpos" then Fen.startPosition else fenArg + val pos = Fen.parse(fen) + println(s"fen: ${Fen.toFen(pos)}") + println(s"side: ${if pos.sideToMove == Color.White then "white" else "black"}") + + val t0 = System.nanoTime() + val nodes = Perft.perft(pos, depth) + val ms = (System.nanoTime() - t0) / 1000000 + val nps = if ms > 0 then nodes * 1000 / ms else 0 + println(s"perft($depth) = $nodes (${ms} ms, ${nps} nps)") + + val searchDepth = math.min(math.max(depth, 4), 6) + val searcher = new Searcher + val searchPos = Fen.parse(fen) + val r = searcher.search(searchPos, searchDepth, 0) + println(s"bestmove ${Move.uci(r.bestMove)} score ${r.score} (depth ${r.depth}, ${searcher.nodes} nodes)") + +private def runSelfPlay(opts: List[String]): Unit = + val maxPlies = opts.headOption.map(_.toInt).getOrElse(200) + val depth = opts.drop(1).headOption.map(_.toInt).getOrElse(5) + val fenArg = opts.drop(2).mkString(" ") + val fen = if fenArg.isEmpty then Fen.startPosition else fenArg + println(s"self-play: maxPlies=$maxPlies depth=$depth") + println(s"start: $fen") + val outcome = Game.selfPlay(fen, maxPlies, depth, 0) + println(s"moves (${outcome.plies}): ${outcome.moves.mkString(" ")}") + println(s"result: ${outcome.result}") + println() + println(Game.pgn(outcome))