diff --git a/minmax-rs/src/connect4/board.rs b/minmax-rs/src/connect4/board.rs index 5c7c39f..4a7362b 100644 --- a/minmax-rs/src/connect4/board.rs +++ b/minmax-rs/src/connect4/board.rs @@ -27,6 +27,10 @@ impl Connect4 { } } + pub fn set_pos(&mut self, position: usize, value: Position) { + self.positions[position] = value; + } + pub fn result(&self) -> State { match self.check_board() { State::Winner(winner) => State::Winner(winner), diff --git a/minmax-rs/src/lib.rs b/minmax-rs/src/lib.rs index 9dd3cfe..3883424 100644 --- a/minmax-rs/src/lib.rs +++ b/minmax-rs/src/lib.rs @@ -92,6 +92,7 @@ impl Score { Self(int) } + #[allow(unused)] fn randomize(self) -> Self { let score = self.0 as f32; let rand = rand::thread_rng(); diff --git a/minmax-rs/src/minmax.rs b/minmax-rs/src/minmax.rs index 07ed8ff..84bcac3 100644 --- a/minmax-rs/src/minmax.rs +++ b/minmax-rs/src/minmax.rs @@ -29,6 +29,10 @@ impl PerfectPlayer { self } + pub fn best_move(&self) -> G::Move { + self.best_move.expect("no move made yet") + } + fn minmax(&mut self, board: &mut G, player: Player, depth: usize) -> Score { if let Some(max_depth) = self.max_depth && depth >= max_depth { return board.rate(player); diff --git a/rs-wrapper/ch_bbw_m411_connect4_RustPlayer.h b/rs-wrapper/ch_bbw_m411_connect4_RustPlayer.h index e6348fd..78516b7 100644 --- a/rs-wrapper/ch_bbw_m411_connect4_RustPlayer.h +++ b/rs-wrapper/ch_bbw_m411_connect4_RustPlayer.h @@ -10,10 +10,10 @@ extern "C" { /* * Class: ch_bbw_m411_connect4_RustPlayer * Method: rustPlay - * Signature: (B[Lch/bbw/m411/connect4/Connect4ArenaMain/Stone;)I + * Signature: (B[B)I */ JNIEXPORT jint JNICALL Java_ch_bbw_m411_connect4_RustPlayer_rustPlay - (JNIEnv *, jobject, jbyte, jobjectArray); + (JNIEnv *, jclass, jbyte, jbyteArray); #ifdef __cplusplus } diff --git a/rs-wrapper/src/lib.rs b/rs-wrapper/src/lib.rs index 149c984..200c458 100644 --- a/rs-wrapper/src/lib.rs +++ b/rs-wrapper/src/lib.rs @@ -1,22 +1,81 @@ -// This is the interface to the JVM that we'll call the majority of our -// methods on. -use jni::JNIEnv; - -// These objects are what you should use as arguments to your native -// function. They carry extra lifetime information to prevent them escaping -// this context and getting used after being GC'd. -use jni::objects::{JClass, JObject}; - -// This is just a pointer. We'll be returning it from our function. We -// can't return one of the objects with lifetime information because the -// lifetime checker won't let us. +use jni::objects::{JClass, JObject, ReleaseMode}; use jni::sys::{jbyte, jint}; +use jni::JNIEnv; use minmax::{connect4::board::Connect4, GamePlayer}; +use minmax::{PerfectPlayer, Player}; -// 0 -> X -// 1 -> O -pub fn wrap_player>(current_player: i8, board: ()) -> i32 { - 0 +/// We need to map the board. +/// Rust: +/// ```text +/// 0 1 2 3 4 5 6 +/// 7 8 9 10 11 12 13 +/// 14 15 16 17 18 19 20 +/// 21 22 23 24 25 26 27 +/// ``` +/// Java: +/// ```text +/// 21 22 23 24 25 26 27 +/// 14 15 16 17 18 19 20 +/// 7 8 9 10 11 12 13 +/// 0 1 2 3 4 5 6 +/// ``` +fn map_idx(i: usize) -> usize { + match () { + () if i < 7 => i + 21, + () if i < 15 => i + 7, + () if i < 21 => i - 7, + () => i - 21, + } +} + +fn crate_board(java_board: &[i8]) -> Connect4 { + let mut board = Connect4::new(); + + for i in 0..28 { + let java_int = java_board[i]; + let rust_value = match java_int { + 0 => Some(Player::X), + 1 => Some(Player::O), + 2 => None, + _ => unreachable!(), + }; + + let rust_index = map_idx(i); + + board.set_pos(rust_index, rust_value); + } + + board +} + +// 0 -> BLUE -> X +// 1 -> RED -> O +// 2 -> empty +pub fn wrap_player(env: JNIEnv<'_>, current_player: i8, board: JObject<'_>) -> i32 { + let board_size = env.get_array_length(board.into_raw()).unwrap(); + assert_eq!(board_size, 28); + + let byte_array = env + .get_byte_array_elements(board.into_raw(), ReleaseMode::NoCopyBack) + .unwrap(); + + let slice = unsafe { std::slice::from_raw_parts(byte_array.as_ptr() as *const _, 28) }; + + let mut board = crate_board(slice); + + let mut player = PerfectPlayer::new(false); + + let current_player_rust = match current_player { + 0 => Player::X, + 1 => Player::O, + _ => unreachable!(), + }; + + player.next_move(&mut board, current_player_rust); + + let result_move = player.best_move(); + + map_idx(result_move) as i32 } // This keeps Rust from "mangling" the name and making it unique for this @@ -27,9 +86,10 @@ pub extern "system" fn Java_ch_bbw_m411_connect4_RustPlayer_rustPlay( // This is the class that owns our static method. It's not going to be used, // but still must be present to match the expected signature of a static // native method. - class: JClass<'_>, + _: JClass<'_>, player: jbyte, board: JObject<'_>, ) -> jint { - 0 + std::panic::catch_unwind(|| wrap_player(env, player, board)) + .unwrap_or_else(|_| std::process::abort()) }