summaryrefslogtreecommitdiff
path: root/engine/src/c_abi.rs
blob: 6a7f35f39c34c8ff74e95cf5cce92359df339270 (plain)
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<Evaluation>,
	best_move: Option<Box<Move>>,
}

#[no_mangle]
extern "C" fn ampere_new_engine(hash_size: c_ulonglong, frontend: &CFrontend) -> Box<Engine<'_>> {
	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<CheckersBitBoard> {
	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<Engine<'_>>) {
	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<CheckersBitBoard> {
	Box::new(CheckersBitBoard::starting_position())
}

#[no_mangle]
extern "C" fn ampere_board_new(
	pieces: u32,
	color: u32,
	kings: u32,
	turn: PieceColor,
) -> Box<CheckersBitBoard> {
	Box::new(CheckersBitBoard::new(pieces, color, kings, turn))
}

#[no_mangle]
extern "C" fn ampere_clock_unlimited() -> Box<Clock> {
	Box::new(Clock::Unlimited)
}

#[no_mangle]
extern "C" fn ampere_clock_timepermove(millis: c_int) -> Box<Clock> {
	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<Clock> {
	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<Clock>) {
	drop(clock)
}

#[no_mangle]
extern "C" fn ampere_board_clone(board: &CheckersBitBoard) -> Box<CheckersBitBoard> {
	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<CheckersBitBoard>) {
	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<Evaluation>) {
	drop(evaluation)
}

#[no_mangle]
extern "C" fn ampere_move_new(start: c_int, direction: MoveDirection, jump: bool) -> Box<Move> {
	Box::new(Move::new(start as usize, direction, jump))
}

#[no_mangle]
extern "C" fn ampere_move_clone(ampere_move: &Move) -> Box<Move> {
	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<Move>) {
	drop(ampere_move)
}