From d415bb7ae975846a39a807cd5211285a72faca4c Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 14 Jan 2023 16:03:33 +0100 Subject: [PATCH] works --- Cargo.lock | 16 ++--- build_and_run.sh | 21 +++++++ .../bbw/m411/connect4/Connect4ArenaMain.java | 60 +------------------ .../java/ch/bbw/m411/connect4/RustPlayer.java | 13 +++- rs-wrapper/Cargo.toml | 2 +- rs-wrapper/src/lib.rs | 58 ++++++++++++------ 6 files changed, 84 insertions(+), 86 deletions(-) create mode 100755 build_and_run.sh diff --git a/Cargo.lock b/Cargo.lock index 2a031d8..9e82801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,14 @@ dependencies = [ "rand", ] +[[package]] +name = "minmax_wrapper" +version = "0.1.0" +dependencies = [ + "jni", + "minmax", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -293,14 +301,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rs-wrapper" -version = "0.1.0" -dependencies = [ - "jni", - "minmax", -] - [[package]] name = "rustix" version = "0.36.6" diff --git a/build_and_run.sh b/build_and_run.sh new file mode 100755 index 0000000..ed1b308 --- /dev/null +++ b/build_and_run.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +if ! which rustc > /dev/null; +then + echo "ERROR: Rust must be installed: https://www.rust-lang.org/tools/install" + exit 1 +fi + +echo "${BASH_SOURCE[0]}" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +echo "Building Rust library..." +cargo build --release "--manifest-path=$SCRIPT_DIR/Cargo.toml" + +export LD_LIBRARY_PATH="$SCRIPT_DIR/target/release" +echo "Setting LD_LIBRARY_PATH to $LD_LIBRARY_PATH" + +echo "Running Java tests. If this fails, there's something wrong :/" +cd "$SCRIPT_DIR/minmax-java" +./gradlew build diff --git a/minmax-java/src/main/java/ch/bbw/m411/connect4/Connect4ArenaMain.java b/minmax-java/src/main/java/ch/bbw/m411/connect4/Connect4ArenaMain.java index 536810a..087f7e3 100644 --- a/minmax-java/src/main/java/ch/bbw/m411/connect4/Connect4ArenaMain.java +++ b/minmax-java/src/main/java/ch/bbw/m411/connect4/Connect4ArenaMain.java @@ -67,65 +67,7 @@ public class Connect4ArenaMain { } boolean isWinning(Stone[] board, Stone forColor) { - var increment = 0; - for (int i = 27; i >= 0; i--) { - if (forColor != board[i]) { - increment = 0; - continue; - } - - // Horizontal reset - if (i == 20 || i == 13 || i == 6) { - increment = 0; - } - - increment++; - // Horizontal - if (increment == 4) { - return true; - } - - if (i > 20) { - // Vertical - var vertical = true; - for (int y = i - 7; y >= 0; y -= 7) { - if (board[y] != forColor) { - vertical = false; - break; - } - } - if (vertical) { - return true; - } - // Diagonally - if (i <= 24) { - var diagonal = true; - for (int y = i; y >= 0; y -= 7 - 1) { - if (board[y] != forColor) { - diagonal = false; - break; - } - } - if (diagonal) { - return true; - } - } - - if (i >= 24) { - var diagonal = true; - for (int y = i; y >= 0; y -= 7 + 1) { - if (board[y] != forColor) { - diagonal = false; - break; - } - } - if (diagonal) { - return true; - } - } - } - } - return false; + return RustPlayer.isWinning(board, forColor); } public enum Stone { diff --git a/minmax-java/src/main/java/ch/bbw/m411/connect4/RustPlayer.java b/minmax-java/src/main/java/ch/bbw/m411/connect4/RustPlayer.java index c28162a..a5f978d 100644 --- a/minmax-java/src/main/java/ch/bbw/m411/connect4/RustPlayer.java +++ b/minmax-java/src/main/java/ch/bbw/m411/connect4/RustPlayer.java @@ -2,12 +2,13 @@ package ch.bbw.m411.connect4; public class RustPlayer extends Connect4ArenaMain.DefaultPlayer { private static native int rustPlay(byte player, byte[] board); + private static native int rustBoardWinner(byte[] board); static { // This actually loads the shared object that we'll be creating. // The actual location of the .so or .dll may differ based on your // platform. - System.loadLibrary("rs_wrapper"); + System.loadLibrary("minmax_wrapper"); } static byte[] encodeBoard(Connect4ArenaMain.Stone[] board) { @@ -27,6 +28,16 @@ public class RustPlayer extends Connect4ArenaMain.DefaultPlayer { return boardBuf; } + public static boolean isWinning(Connect4ArenaMain.Stone[] board, Connect4ArenaMain.Stone forColor) { + byte[] boardBuf = RustPlayer.encodeBoard(board); + byte expectedPlayer = switch (forColor) { + case BLUE -> 1; + case RED -> 0; + }; + int winner = RustPlayer.rustBoardWinner(boardBuf); + return winner == expectedPlayer; + } + @Override protected int play() { byte player = switch (this.myColor) { diff --git a/rs-wrapper/Cargo.toml b/rs-wrapper/Cargo.toml index 8a29835..a47d8cc 100644 --- a/rs-wrapper/Cargo.toml +++ b/rs-wrapper/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rs-wrapper" +name = "minmax_wrapper" version = "0.1.0" edition = "2021" diff --git a/rs-wrapper/src/lib.rs b/rs-wrapper/src/lib.rs index 9a3493e..a4f6823 100644 --- a/rs-wrapper/src/lib.rs +++ b/rs-wrapper/src/lib.rs @@ -2,7 +2,7 @@ use jni::objects::{JClass, JObject, ReleaseMode}; use jni::sys::{jbyte, jint}; use jni::JNIEnv; use minmax::{connect4::board::Connect4, GamePlayer}; -use minmax::{Game, PerfectPlayer, Player}; +use minmax::{Game, PerfectPlayer, Player, State}; /// We need to map the board. /// Rust: @@ -28,7 +28,16 @@ fn map_idx(i: usize) -> usize { } } -fn crate_board(java_board: &[i8]) -> Connect4 { +unsafe fn create_board(env: JNIEnv<'_>, board: JObject<'_>) -> Connect4 { + 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 java_board = unsafe { std::slice::from_raw_parts(byte_array.as_ptr().cast::(), 28) }; + let mut board = Connect4::new(); for i in 0..28 { @@ -37,7 +46,9 @@ fn crate_board(java_board: &[i8]) -> Connect4 { 0 => Some(Player::X), 1 => Some(Player::O), 2 => None, - _ => unreachable!(), + _ => unreachable!( + "Java code passed an invalid value as the byte of the board: {java_int}" + ), }; let rust_index = map_idx(i); @@ -51,17 +62,8 @@ fn crate_board(java_board: &[i8]) -> Connect4 { // 0 -> RED -> X // 1 -> BLUE -> 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); +pub fn play_move(env: JNIEnv<'_>, current_player: i8, board: JObject<'_>) -> i32 { + let mut board = unsafe { create_board(env, board) }; let mut player = PerfectPlayer::new(false); @@ -82,8 +84,18 @@ pub fn wrap_player(env: JNIEnv<'_>, current_player: i8, board: JObject<'_>) -> i java_idx } -// This keeps Rust from "mangling" the name and making it unique for this -// crate. +fn board_winner(env: JNIEnv<'_>, board: JObject<'_>) -> i32 { + let board = unsafe { create_board(env, board) }; + + let state = board.result(); + match state { + State::Draw => 2, + State::InProgress => 2, + State::Winner(Player::X) => 0, + State::Winner(Player::O) => 1, + } +} + #[no_mangle] pub extern "system" fn Java_ch_bbw_m411_connect4_RustPlayer_rustPlay( env: JNIEnv<'_>, @@ -94,6 +106,18 @@ pub extern "system" fn Java_ch_bbw_m411_connect4_RustPlayer_rustPlay( player: jbyte, board: JObject<'_>, ) -> jint { - std::panic::catch_unwind(|| wrap_player(env, player, board)) + std::panic::catch_unwind(|| play_move(env, player, board)) .unwrap_or_else(|_| std::process::abort()) } + +#[no_mangle] +pub extern "system" fn Java_ch_bbw_m411_connect4_RustPlayer_rustBoardWinner( + env: JNIEnv<'_>, + // 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. + _: JClass<'_>, + board: JObject<'_>, +) -> jint { + std::panic::catch_unwind(|| board_winner(env, board)).unwrap_or_else(|_| std::process::abort()) +}