use core::ffi::{c_int, c_ulonglong}; use std::ffi::CString; use std::num::{NonZeroU8, NonZeroUsize}; use std::sync::atomic::AtomicBool; use std::time::Duration; use model::{CheckersBitBoard, Move, MoveDirection, PieceColor}; use crate::{ ActualLimit, Clock, Engine, EvalInfo, Evaluation, EvaluationSettings, Frontend, SearchLimit, }; #[repr(C)] struct CFrontend { debug_fn: extern "C" fn(*const u8), info_fn: extern "C" fn(*const EvalInfo), bestmove_fn: extern "C" fn(*const Move), } impl Frontend for CFrontend { fn debug(&self, msg: &str) { (self.debug_fn)(msg.as_bytes().as_ptr()) } fn info(&self, info: EvalInfo) { (self.info_fn)(&info) } fn report_best_move(&self, best_move: Move) { (self.bestmove_fn)(&best_move) } } #[repr(C)] struct EvalResult { evaluation: Box, best_move: Option>, } #[no_mangle] extern "C" fn ampere_new_engine(hash_size: c_ulonglong, frontend: &CFrontend) -> Box> { Box::new(Engine::new(hash_size as usize, frontend)) } #[no_mangle] extern "C" fn ampere_set_debug(engine: &Engine<'_>, debug: bool) { engine.set_debug(debug) } #[no_mangle] extern "C" fn ampere_islegal(engine: &Engine<'_>, ampere_move: &Move) -> bool { engine.is_legal_move(*ampere_move) } #[no_mangle] extern "C" fn ampere_current_position(engine: &Engine<'_>) -> Box { Box::new(engine.current_position()) } #[no_mangle] extern "C" fn ampere_reset_position(engine: &Engine<'_>) { engine.reset_position(); } #[no_mangle] extern "C" fn ampere_set_position(engine: &Engine<'_>, board: &CheckersBitBoard) { engine.set_position(*board); } #[no_mangle] extern "C" fn ampere_play_move(engine: &Engine<'_>, ampere_move: &Move) -> bool { engine.apply_move(*ampere_move).is_some() } #[no_mangle] extern "C" fn ampere_evaluate( engine: &'static Engine<'_>, cancel: Option<&AtomicBool>, nodes: c_int, depth: c_int, time: Option<&Clock>, ) -> EvalResult { let limits = if nodes == 0 && depth == 0 && time.is_none() { SearchLimit::Auto } else { let time = time.cloned().unwrap_or(Clock::Unlimited); SearchLimit::Limited(ActualLimit { nodes: NonZeroUsize::new(nodes as usize), depth: NonZeroU8::new(depth as u8), time: Some(time.recommended_time(engine.current_position().turn)), }) }; let (eval, best) = engine.evaluate( cancel, EvaluationSettings { restrict_moves: None, ponder: false, clock: Clock::Unlimited, search_until: limits, }, ); let evaluation = Box::new(eval); let best_move = best.map(Box::new); EvalResult { evaluation, best_move, } } #[no_mangle] extern "C" fn ampere_starteval_limited( engine: &'static Engine<'_>, ponder: bool, nodes: c_int, depth: c_int, time: c_int, ) { let limits = if nodes == 0 && depth == 0 && time == 0 { SearchLimit::Auto } else { let time = if time == 0 { None } else { Some(Duration::from_millis(time as u64)) }; SearchLimit::Limited(ActualLimit { nodes: NonZeroUsize::new(nodes as usize), depth: NonZeroU8::new(depth as u8), time, }) }; engine.start_evaluation(EvaluationSettings { restrict_moves: None, ponder, clock: Clock::Unlimited, search_until: limits, }) } #[no_mangle] extern "C" fn ampere_starteval_unlimited(engine: &'static Engine<'_>, ponder: bool) { engine.start_evaluation(EvaluationSettings { restrict_moves: None, ponder, clock: Clock::Unlimited, search_until: SearchLimit::Infinite, }) } #[no_mangle] extern "C" fn ampere_stopeval(engine: &Engine<'_>) -> bool { engine.stop_evaluation().is_some() } #[no_mangle] extern "C" fn ampere_destroy_engine(engine: Box>) { drop(engine) } #[no_mangle] extern "C" fn ampere_evalinfo_nodes(info: &EvalInfo) -> c_ulonglong { info.nodes_searched as c_ulonglong } #[no_mangle] extern "C" fn ampere_evalinfo_evaluation(info: &EvalInfo) -> *const Evaluation { &info.evaluation } #[no_mangle] extern "C" fn ampere_evalinfo_bestmove(info: &EvalInfo) -> Option<&Move> { info.current_best_move.as_ref() } #[no_mangle] extern "C" fn ampere_evalinfo_depth(info: &EvalInfo) -> c_int { info.current_depth as c_int } #[no_mangle] extern "C" fn ampere_evalinfo_nodespersec(info: &EvalInfo) -> c_ulonglong { info.nodes_per_second() as c_ulonglong } #[no_mangle] extern "C" fn ampere_evalinfo_elapsed(info: &EvalInfo) -> c_ulonglong { info.elapsed_milliseconds() as c_ulonglong } #[no_mangle] extern "C" fn ampere_board_starting_position() -> Box { Box::new(CheckersBitBoard::starting_position()) } #[no_mangle] extern "C" fn ampere_board_new( pieces: u32, color: u32, kings: u32, turn: PieceColor, ) -> Box { Box::new(CheckersBitBoard::new(pieces, color, kings, turn)) } #[no_mangle] extern "C" fn ampere_clock_unlimited() -> Box { Box::new(Clock::Unlimited) } #[no_mangle] extern "C" fn ampere_clock_timepermove(millis: c_int) -> Box { Box::new(Clock::TimePerMove(Duration::from_millis(millis as u64))) } #[no_mangle] extern "C" fn ampere_clock_incremental( white_time: c_int, black_time: c_int, white_inc: c_int, black_inc: c_int, moves_to_time_control: c_int, time_control: c_int, ) -> Box { let moves_until_next_time_control = if time_control == 0 { None } else { Some(( moves_to_time_control as u32, Duration::from_millis(time_control as u64), )) }; Box::new(Clock::Incremental { white_time_remaining: Duration::from_millis(white_time as u64), black_time_remaining: Duration::from_millis(black_time as u64), white_increment: Duration::from_millis(white_inc as u64), black_increment: Duration::from_millis(black_inc as u64), moves_until_next_time_control, }) } #[no_mangle] extern "C" fn ampere_clock_destroy(clock: Box) { drop(clock) } #[no_mangle] extern "C" fn ampere_board_clone(board: &CheckersBitBoard) -> Box { Box::new(*board) } #[no_mangle] extern "C" fn ampere_board_equal(a: &CheckersBitBoard, b: &CheckersBitBoard) -> bool { *a == *b } #[no_mangle] extern "C" fn ampere_board_hash(board: &CheckersBitBoard) -> u64 { board.hash_code() } #[no_mangle] extern "C" fn ampere_board_pieces(board: &mut CheckersBitBoard) -> &mut u32 { &mut board.pieces } #[no_mangle] extern "C" fn ampere_board_colors(board: &mut CheckersBitBoard) -> &mut u32 { &mut board.color } #[no_mangle] extern "C" fn ampere_board_kings(board: &mut CheckersBitBoard) -> &mut u32 { &mut board.kings } #[no_mangle] extern "C" fn ampere_board_turn(board: &mut CheckersBitBoard) -> &mut PieceColor { &mut board.turn } #[no_mangle] extern "C" fn ampere_board_has_piece_at(board: &CheckersBitBoard, square: c_int) -> bool { board.piece_at(square as usize) } #[no_mangle] unsafe extern "C" fn ampere_board_color_at(board: &CheckersBitBoard, square: c_int) -> PieceColor { board.color_at_unchecked(square as usize) } #[no_mangle] unsafe extern "C" fn ampere_board_king_at(board: &CheckersBitBoard, square: c_int) -> bool { board.king_at_unchecked(square as usize) } #[no_mangle] unsafe extern "C" fn ampere_board_move_piece( board: &mut CheckersBitBoard, start: c_int, dest: c_int, ) { *board = board.move_piece_to_unchecked(start as usize, dest as usize); } #[no_mangle] extern "C" fn ampere_board_clear_piece(board: &mut CheckersBitBoard, square: c_int) { *board = board.clear_piece(square as usize); } #[no_mangle] extern "C" fn ampere_board_destroy(board: Box) { drop(board) } #[no_mangle] extern "C" fn ampere_eval_is_force_win(evaluation: &Evaluation) -> bool { evaluation.is_force_win() } #[no_mangle] extern "C" fn ampere_eval_is_force_loss(evaluation: &Evaluation) -> bool { evaluation.is_force_loss() } #[no_mangle] extern "C" fn ampere_eval_is_force_seq(evaluation: &Evaluation) -> bool { evaluation.is_force_sequence() } #[no_mangle] unsafe extern "C" fn ampere_eval_forceseq_len(evaluation: &Evaluation) -> c_int { evaluation.force_sequence_length().unwrap_unchecked() as c_int } #[no_mangle] unsafe extern "C" fn ampere_eval_tofloat(evaluation: &Evaluation) -> f32 { evaluation.to_f32_unchecked() } #[no_mangle] extern "C" fn ampere_eval_destroy(evaluation: Box) { drop(evaluation) } #[no_mangle] extern "C" fn ampere_move_new(start: c_int, direction: MoveDirection, jump: bool) -> Box { Box::new(Move::new(start as usize, direction, jump)) } #[no_mangle] extern "C" fn ampere_move_clone(ampere_move: &Move) -> Box { Box::new(*ampere_move) } #[no_mangle] extern "C" fn ampere_move_equal(a: &Move, b: &Move) -> bool { *a == *b } #[no_mangle] unsafe extern "C" fn ampere_move_string(m: &Move, buffer: *mut u8) { let buffer = std::slice::from_raw_parts_mut(buffer, 6); let string = CString::new(m.to_string().as_bytes()).unwrap_unchecked(); let bytes = string.as_bytes_with_nul(); buffer[..bytes.len()].copy_from_slice(bytes) } #[no_mangle] extern "C" fn ampere_move_start(ampere_move: &Move) -> c_int { ampere_move.start() as c_int } #[no_mangle] extern "C" fn ampere_move_direction(ampere_move: &Move) -> MoveDirection { ampere_move.direction() } #[no_mangle] extern "C" fn ampere_move_is_jump(ampere_move: &Move) -> bool { ampere_move.is_jump() } #[no_mangle] unsafe extern "C" fn ampere_move_jump_position(ampere_move: &Move) -> c_int { ampere_move.jump_position() as c_int } #[no_mangle] extern "C" fn ampere_move_end(ampere_move: &Move) -> c_int { ampere_move.end_position() as c_int } #[no_mangle] extern "C" fn ampere_move_destroy(ampere_move: Box) { drop(ampere_move) }