/// Command-line front end for the moonchess library.
///|
/// Parse a small non-negative integer, falling back on invalid input.
fn parse_int_arg(s : String, fallback : Int32) -> Int32 {
if s.length() == 0 {
return fallback
}
let mut value = 0
for ch in s {
let d = ch.to_int() - '0'.to_int()
if d < 0 || d > 9 {
return fallback
}
value = value * 10 + d
}
value
}
///|
/// Build a game from the optional FEN argument at `index`.
fn game_from(args : Array[String], index : Int32) -> @lib.Chess {
if args.length() > index {
match @lib.Chess::from_fen(args[index]) {
Some(game) => game
None => {
println("invalid FEN, falling back to the start position")
@lib.Chess::new()
}
}
} else {
@lib.Chess::new()
}
}
///|
/// Play a self-play game, printing one SAN move per line.
/// Uses the opening book for the first few plies, then the engine.
fn selfplay(depth : Int32, max_plies : Int32) -> Unit {
let game = @lib.Chess::new()
let mut played = 0
while played < max_plies && !game.is_game_over() {
let book = if played < 6 { game.book_move() } else { None }
match book {
Some(m) => {
println(m.san)
played = played + 1
}
None =>
match game.best_move(depth) {
Some(m) => {
println(m.san)
game.make_move(m)
played = played + 1
}
None => break
}
}
}
println("---")
println(game.fen())
if game.is_checkmate() {
println("checkmate")
} else if game.is_stalemate() {
println("stalemate")
} else if game.is_draw() {
println("draw")
} else {
println("stopped after \{played} plies")
}
}
///|
/// Print usage information.
fn usage() -> Unit {
println("moonchess - a chess library and engine for MoonBit")
println("")
println("Usage:")
println(
" moonchess perft perft node count from the start",
)
println(" moonchess divide perft split by root move")
println(
" moonchess best [fen] best move for the side to move",
)
println(
" moonchess think [fen] iterative-deepening search with a TT",
)
println(" moonchess timed [fen] time-limited search")
println(" moonchess selfplay [plies] print a self-play game in SAN")
println(
" moonchess book play moves, then print a book reply",
)
println(" moonchess board [fen] print the board and FEN")
println(" moonchess validate check that a FEN is legal")
println(" moonchess fen [fen] echo a FEN")
}
///|
fn main {
let args = @env.args()
if args.length() < 2 {
usage()
return
}
match args[1] {
"perft" => {
let depth = if args.length() > 2 { parse_int_arg(args[2], 3) } else { 3 }
let game = @lib.Chess::new()
println("perft(\{depth}) = \{game.perft(depth)}")
}
"divide" => {
let depth = if args.length() > 2 { parse_int_arg(args[2], 3) } else { 3 }
let rows = @lib.Chess::new().perft_divide(depth)
let mut total = 0
for row in rows {
println("\{row.0}: \{row.1}")
total = total + row.1
}
println("total: \{total}")
}
"best" => {
let depth = if args.length() > 2 { parse_int_arg(args[2], 3) } else { 3 }
let game = game_from(args, 3)
let (best, score) = game.best_move_scored(depth)
match best {
Some(m) =>
println("best: \{m.san} (score \{score} cp at depth \{depth})")
None => println("no legal move (game over)")
}
}
"think" => {
let depth = if args.length() > 2 { parse_int_arg(args[2], 4) } else { 4 }
let game = game_from(args, 3)
let searcher = @lib.Searcher::new()
let result = searcher.search(game, depth)
match result.best {
Some(m) =>
println(
"best: \{m.san} (score \{result.score} cp, depth \{result.depth}, nodes \{result.nodes})",
)
None => println("no legal move (game over)")
}
}
"timed" => {
let budget = if args.length() > 2 {
parse_int_arg(args[2], 1000)
} else {
1000
}
let game = game_from(args, 3)
let searcher = @lib.Searcher::new()
let result = searcher.search_timed(game, 64, budget)
match result.best {
Some(m) =>
println(
"best: \{m.san} (score \{result.score} cp, depth \{result.depth}, nodes \{result.nodes})",
)
None => println("no legal move (game over)")
}
}
"selfplay" => {
let depth = if args.length() > 2 { parse_int_arg(args[2], 3) } else { 3 }
let plies = if args.length() > 3 {
parse_int_arg(args[3], 40)
} else {
40
}
selfplay(depth, plies)
}
"book" => {
let game = @lib.Chess::new()
let mut i = 2
while i < args.length() {
ignore(game.move_by_san(args[i]))
i = i + 1
}
match game.book_move() {
Some(m) => println("book: \{m.san}")
None => println("book: (no known continuation)")
}
}
"board" => {
let game = game_from(args, 2)
println(game.display())
println(game.fen())
}
"fen" => {
let game = game_from(args, 2)
println(game.fen())
}
"validate" => {
let fen = if args.length() > 2 { args[2] } else { "" }
match @lib.validate_fen(fen) {
Some(msg) => println("invalid: \{msg}")
None => println("valid")
}
}
_ => usage()
}
}