From c235e4ec6532ff91b4220f044dd69a5365a5c12d Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 15 Jan 2023 15:36:54 +0100 Subject: [PATCH] port to java --- .../bbw/m411/connect4/Connect4ArenaMain.java | 4 +- .../ch/bbw/m411/connect4/PerfectPlayer.java | 76 +++++++++++++++++++ .../java/ch/bbw/m411/connect4/RustPlayer.java | 23 +++++- 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 minmax-java/src/main/java/ch/bbw/m411/connect4/PerfectPlayer.java 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 087f7e3..0411b7c 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 @@ -17,7 +17,7 @@ public class Connect4ArenaMain { static final int NOMOVE = -1; public static void main(String[] args) { - new Connect4ArenaMain().play(new RustPlayer(), new GreedyPlayer()); + new Connect4ArenaMain().play(new HumanPlayer(), new PerfectPlayer()); } static String toDebugString(Stone[] board) { @@ -66,7 +66,7 @@ public class Connect4ArenaMain { return null; // null implies a draw } - boolean isWinning(Stone[] board, Stone forColor) { + public static boolean isWinning(Stone[] board, Stone forColor) { return RustPlayer.isWinning(board, forColor); } diff --git a/minmax-java/src/main/java/ch/bbw/m411/connect4/PerfectPlayer.java b/minmax-java/src/main/java/ch/bbw/m411/connect4/PerfectPlayer.java new file mode 100644 index 0000000..79ca215 --- /dev/null +++ b/minmax-java/src/main/java/ch/bbw/m411/connect4/PerfectPlayer.java @@ -0,0 +1,76 @@ +package ch.bbw.m411.connect4; + +import java.util.ArrayList; +import java.util.List; + +public class PerfectPlayer extends Connect4ArenaMain.DefaultPlayer { + private static final int MAX_DEPTH = 8; + private static final int WON = 1000; + private static final int LOST = -1000; + + private int bestMove = -1; + + private List possibleMoves() { + var moves = new ArrayList(); + // for all columns + for (int i = 0; i < 7; i++) { + // walk up + for (int j = 0; j < 4; j++) { + var position = i + (j * 7); + if (board[position] == null) { + moves.add(position); + break; + } + } + + } + return moves; + } + + private int minmax(Connect4ArenaMain.Stone maximizingPlayer, int alpha, int beta, int depth) { + if (depth >= MAX_DEPTH) { + // FIXME rate + return 0; + } + + if (Connect4ArenaMain.isWinning(board, maximizingPlayer)) { + return WON; + } + + if (Connect4ArenaMain.isWinning(board, maximizingPlayer.opponent())) { + return LOST; + } + + var moves = possibleMoves(); + if (moves.isEmpty()) { + return 0; + } + + var maxValue = alpha; + for (var move : moves) { + board[move] = maximizingPlayer; + var value = -minmax(maximizingPlayer.opponent(), -beta, -maxValue, depth + 1); + board[move] = null; + + if (value > maxValue) { + maxValue = value; + if (depth == 0) { + bestMove = move; + } + + if (maxValue >= beta) { + break; + } + } + } + + return maxValue; + } + + @Override + protected int play() { + bestMove = -1; + minmax(myColor, LOST, WON, 0); + return this.bestMove; + } +} 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 a5f978d..e91c043 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 @@ -1,14 +1,28 @@ package ch.bbw.m411.connect4; +import java.io.File; +import java.io.IOException; + 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("minmax_wrapper"); + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + var path = "target/release/" + System.mapLibraryName("minmax_wrapper"); + + try { + System.load(new File(path).getCanonicalPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + // 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("minmax_wrapper"); + } } static byte[] encodeBoard(Connect4ArenaMain.Stone[] board) { @@ -29,6 +43,7 @@ public class RustPlayer extends Connect4ArenaMain.DefaultPlayer { } public static boolean isWinning(Connect4ArenaMain.Stone[] board, Connect4ArenaMain.Stone forColor) { + byte[] boardBuf = RustPlayer.encodeBoard(board); byte expectedPlayer = switch (forColor) { case BLUE -> 1;