From ac8486962d2f6b262d8bcfc5b5b879178bc9e47d Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:57:12 +0100 Subject: [PATCH] is winning --- .../bbw/m411/connect4/Connect4ArenaMain.java | 373 ++++++++++-------- .../java/ch/bbw/m411/connect4/RustPlayer.java | 11 - 2 files changed, 216 insertions(+), 168 deletions(-) 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 81232a5..536810a 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 @@ -10,185 +10,244 @@ import java.util.Scanner; */ public class Connect4ArenaMain { - static final int WIDTH = 7; + static final int WIDTH = 7; - static final int HEIGHT = 4; + static final int HEIGHT = 4; - static final int NOMOVE = -1; + static final int NOMOVE = -1; - public static void main(String[] args) { - new Connect4ArenaMain().play(new RustPlayer(), new GreedyPlayer()); - } + public static void main(String[] args) { + new Connect4ArenaMain().play(new RustPlayer(), new GreedyPlayer()); + } - static String toDebugString(Stone[] board) { - var sb = new StringBuilder(); - for (int r = 0; r < HEIGHT; r++) { - for (int c = 0; c < WIDTH; c++) { - var value = board[r * WIDTH + c]; - sb.append(value == null ? "." : (value == Stone.RED ? "X" : "O")); - } - sb.append("-"); - } - return sb.toString(); - } + static String toDebugString(Stone[] board) { + var sb = new StringBuilder(); + for (int r = 0; r < HEIGHT; r++) { + for (int c = 0; c < WIDTH; c++) { + var value = board[r * WIDTH + c]; + sb.append(value == null ? "." : (value == Stone.RED ? "X" : "O")); + } + sb.append("-"); + } + return sb.toString(); + } - Connect4Player play(Connect4Player red, Connect4Player blue) { - if (red == blue) { - throw new IllegalStateException("must be different players (simply create two instances)"); - } - var board = new Stone[WIDTH * HEIGHT]; - red.initialize(Arrays.copyOf(board, board.length), Stone.RED); - blue.initialize(Arrays.copyOf(board, board.length), Stone.BLUE); - var lastMove = NOMOVE; - var currentPlayer = red; - for (int round = 0; round < board.length; round++) { - var currentColor = currentPlayer == red ? Stone.RED : Stone.BLUE; - System.out.println(HumanPlayer.toPrettyString(board) + currentColor + " to play next..."); - lastMove = currentPlayer.play(lastMove); - if (lastMove < 0 || lastMove >= WIDTH * HEIGHT) { - throw new IllegalStateException("move is outside of valid range: " + lastMove); - } - if (board[lastMove] != null) { - throw new IllegalStateException("position " + lastMove + " is already occupied @" + toDebugString(board)); - } - if (lastMove > WIDTH && board[lastMove - WIDTH] == null) { - throw new IllegalStateException("position " + lastMove + " is mid-air @" + toDebugString(board)); - } - board[lastMove] = currentColor; - if (isWinning(board, currentColor)) { - System.out.println( - HumanPlayer.toPrettyString(board) + "...and the winner is: " + currentColor + " @ " + toDebugString(board)); - return currentPlayer; - } - currentPlayer = currentPlayer == red ? blue : red; - } - System.out.println(HumanPlayer.toPrettyString(board) + "...it's a DRAW @ " + toDebugString(board)); - return null; // null implies a draw - } + Connect4Player play(Connect4Player red, Connect4Player blue) { + if (red == blue) { + throw new IllegalStateException("must be different players (simply create two instances)"); + } + var board = new Stone[WIDTH * HEIGHT]; + red.initialize(Arrays.copyOf(board, board.length), Stone.RED); + blue.initialize(Arrays.copyOf(board, board.length), Stone.BLUE); + var lastMove = NOMOVE; + var currentPlayer = red; + for (int round = 0; round < board.length; round++) { + var currentColor = currentPlayer == red ? Stone.RED : Stone.BLUE; + System.out.println(HumanPlayer.toPrettyString(board) + currentColor + " to play next..."); + lastMove = currentPlayer.play(lastMove); + if (lastMove < 0 || lastMove >= WIDTH * HEIGHT) { + throw new IllegalStateException("move is outside of valid range: " + lastMove); + } + if (board[lastMove] != null) { + throw new IllegalStateException("position " + lastMove + " is already occupied @" + toDebugString(board)); + } + if (lastMove > WIDTH && board[lastMove - WIDTH] == null) { + throw new IllegalStateException("position " + lastMove + " is mid-air @" + toDebugString(board)); + } + board[lastMove] = currentColor; + if (isWinning(board, currentColor)) { + System.out.println( + HumanPlayer.toPrettyString(board) + "...and the winner is: " + currentColor + " @ " + toDebugString(board)); + return currentPlayer; + } + currentPlayer = currentPlayer == red ? blue : red; + } + System.out.println(HumanPlayer.toPrettyString(board) + "...it's a DRAW @ " + toDebugString(board)); + return null; // null implies a draw + } - boolean isWinning(Stone[] board, Stone forColor) { - // TODO: provide an implementation - throw new IllegalStateException("Not implemented yet"); - } + boolean isWinning(Stone[] board, Stone forColor) { + var increment = 0; + for (int i = 27; i >= 0; i--) { + if (forColor != board[i]) { + increment = 0; + continue; + } - public enum Stone { - RED, BLUE; + // Horizontal reset + if (i == 20 || i == 13 || i == 6) { + increment = 0; + } - public Stone opponent() { - return this == RED ? BLUE : RED; - } - } + increment++; + // Horizontal + if (increment == 4) { + return true; + } - public interface Connect4Player { + 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; + } + } - /** - * Called before the game starts and guaranteed to only be called once per livetime of the player. - * - * @param board the starting board, usually an empty board. - * @param colorToPlay the color of this player - */ - void initialize(Stone[] board, Stone colorToPlay); + 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; + } - /** - * Perform a next move, will only be called if the Game is not over yet. - * Each player has to keep an internal state of the 4x7 board, wher the 0-index is on the bottom row. - * The index-layout looks as: - *
- * 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 - *- * - * @param opponendPlayed the last index where the opponent played to (in range 0 - width*height exclusive) - * or -1 if this is the first move. - * @return an index to play to (in range 0 - width*height exclusive) - */ - int play(int opponendPlayed); - } + public enum Stone { + RED, BLUE; - /** - * An abstract helper class to keep track of a board (and whatever we or the opponent played). - */ - public abstract static class DefaultPlayer implements Connect4Player { + public Stone opponent() { + return this == RED ? BLUE : RED; + } + } - Stone[] board; + public interface Connect4Player { - Stone myColor; + /** + * Called before the game starts and guaranteed to only be called once per livetime of the player. + * + * @param board the starting board, usually an empty board. + * @param colorToPlay the color of this player + */ + void initialize(Stone[] board, Stone colorToPlay); - @Override - public void initialize(Stone[] board, Stone colorToPlay) { - this.board = board; - myColor = colorToPlay; - } + /** + * Perform a next move, will only be called if the Game is not over yet. + * Each player has to keep an internal state of the 4x7 board, wher the 0-index is on the bottom row. + * The index-layout looks as: + *
+ * 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 + *+ * + * @param opponendPlayed the last index where the opponent played to (in range 0 - width*height exclusive) + * or -1 if this is the first move. + * @return an index to play to (in range 0 - width*height exclusive) + */ + int play(int opponendPlayed); + } - @Override - public int play(int opponendPlayed) { - if (opponendPlayed != NOMOVE) { - board[opponendPlayed] = myColor.opponent(); - } - var playTo = play(); - board[playTo] = myColor; - return playTo; - } + /** + * An abstract helper class to keep track of a board (and whatever we or the opponent played). + */ + public abstract static class DefaultPlayer implements Connect4Player { - /** - * Givent the current {@link #board}, find a suitable position-index to play to. - * @return the position to play to as defined by {@link Connect4Player#play(int)}. - */ - protected abstract int play(); + Stone[] board; - } + Stone myColor; - public static class HumanPlayer extends DefaultPlayer { + @Override + public void initialize(Stone[] board, Stone colorToPlay) { + this.board = board; + myColor = colorToPlay; + } - static String toPrettyString(Stone[] board) { - var sb = new StringBuilder(); - for (int r = HEIGHT - 1; r >= 0; r--) { - for (int c = 0; c < WIDTH; c++) { - var index = r * WIDTH + c; - if (board[index] == null) { - if (index < WIDTH || board[index - WIDTH] != null) { - sb.append("\033[37m" + index + "\033[0m "); - if (index < 10) { - sb.append(" "); - } - } else { - sb.append("\033[37m.\033[0m "); - } - } else if (board[index] == Stone.RED) { - sb.append("\033[1;31mX\033[0m "); - } else { - sb.append("\033[1;34mO\033[0m "); - } - } - sb.append("\n"); - } - return sb.toString(); - } - @Override - protected int play() { - System.out.println("where to to put the next " + myColor + "?"); - var scanner = new Scanner(System.in, StandardCharsets.UTF_8); - return Integer.parseInt(scanner.nextLine()); - } + @Override + public int play(int opponendPlayed) { + if (opponendPlayed != NOMOVE) { + board[opponendPlayed] = myColor.opponent(); + } + var playTo = play(); + board[playTo] = myColor; + return playTo; + } - } + /** + * Givent the current {@link #board}, find a suitable position-index to play to. + * + * @return the position to play to as defined by {@link Connect4Player#play(int)}. + */ + protected abstract int play(); - public static class GreedyPlayer extends DefaultPlayer { + } - @Override - protected int play() { - for (int c = 0; c < WIDTH; c++) { - for (int r = 0; r < HEIGHT; r++) { - var index = r * WIDTH + c; - if (board[index] == null) { - return index; - } - } - } - throw new IllegalStateException("cannot play at all"); - } - } + public static class HumanPlayer extends DefaultPlayer { + + static String toPrettyString(Stone[] board) { + var sb = new StringBuilder(); + for (int r = HEIGHT - 1; r >= 0; r--) { + for (int c = 0; c < WIDTH; c++) { + var index = r * WIDTH + c; + if (board[index] == null) { + if (index < WIDTH || board[index - WIDTH] != null) { + sb.append("\033[37m" + index + "\033[0m "); + if (index < 10) { + sb.append(" "); + } + } else { + sb.append("\033[37m.\033[0m "); + } + } else if (board[index] == Stone.RED) { + sb.append("\033[1;31mX\033[0m "); + } else { + sb.append("\033[1;34mO\033[0m "); + } + } + sb.append("\n"); + } + return sb.toString(); + } + + @Override + protected int play() { + System.out.println("where to to put the next " + myColor + "?"); + var scanner = new Scanner(System.in, StandardCharsets.UTF_8); + return Integer.parseInt(scanner.nextLine()); + } + + } + + public static class GreedyPlayer extends DefaultPlayer { + + @Override + protected int play() { + for (int c = 0; c < WIDTH; c++) { + for (int r = 0; r < HEIGHT; r++) { + var index = r * WIDTH + c; + if (board[index] == null) { + return index; + } + } + } + throw new IllegalStateException("cannot play at all"); + } + } } 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 d184a16..c3e52fb 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 @@ -3,8 +3,6 @@ package ch.bbw.m411.connect4; public class RustPlayer extends Connect4ArenaMain.DefaultPlayer { private static native int rustPlay(byte player, byte[] board); - private static native boolean isWinning(byte player, byte[] forColor); - 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 @@ -29,15 +27,6 @@ public class RustPlayer extends Connect4ArenaMain.DefaultPlayer { return boardBuf; } - public static boolean isWinning(Connect4ArenaMain.Stone[] board, Connect4ArenaMain.Stone forColor) { - byte player = switch (forColor) { - case BLUE -> 0; - case RED -> 1; - }; - byte[] boardBuf = RustPlayer.encodeBoard(board); - return RustPlayer.isWinning(player, boardBuf); - } - @Override protected int play() { byte player = switch (this.myColor) {