diff --git a/src/main/java/featurecat/lizzie/gui/Menu.java b/src/main/java/featurecat/lizzie/gui/Menu.java index 7985e9f88..fdc9ee2a4 100644 --- a/src/main/java/featurecat/lizzie/gui/Menu.java +++ b/src/main/java/featurecat/lizzie/gui/Menu.java @@ -1162,6 +1162,23 @@ public void actionPerformed(ActionEvent e) { } }); gameMenu.add(pass); + + final JMenuItem continueLadder = + new JMenuItem(resourceBundle.getString("Menu.game.continueLadder")); + continueLadder.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (Lizzie.board.continueLadder() == 0) + JOptionPane.showMessageDialog( + Lizzie.frame, + String.format( + resourceBundle.getString("Menu.game.continueLadderFail"), + Lizzie.board.MINIMUM_LADDER_LENGTH_FOR_AUTO_CONTINUATION)); + } + }); + gameMenu.add(continueLadder); + gameMenu.addSeparator(); final JMenuItem clearBoard = new JMenuItem(resourceBundle.getString("Menu.game.clearBoard")); diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java index 8d528fa36..221e6fb04 100644 --- a/src/main/java/featurecat/lizzie/rules/Board.java +++ b/src/main/java/featurecat/lizzie/rules/Board.java @@ -614,6 +614,47 @@ public void place(String namedCoordinate) { } } + // at least 5. increase this to avoid wrong detection of ladders + public final int MINIMUM_LADDER_LENGTH_FOR_AUTO_CONTINUATION = 5; + + public int continueLadder() { + int k; + // Repeating continueLadderByOne() is inefficient. So what? :p + for (k = 0; continueLadderByOne(); k++) ; + Lizzie.frame.refresh(); + return k; + } + + private boolean continueLadderByOne() { + final int PERIOD = 4, CHECK_LENGTH = MINIMUM_LADDER_LENGTH_FOR_AUTO_CONTINUATION; + BoardHistoryList copiedHistory = history.shallowCopy(); + int[][] pastMove = new int[CHECK_LENGTH][]; + int dx = 0, dy = 0; + for (int k = 0; k < CHECK_LENGTH; k++) { + Optional lastMoveOpt = copiedHistory.getLastMove(); + if (!lastMoveOpt.isPresent()) return false; + int[] move = pastMove[k] = lastMoveOpt.get(); + copiedHistory.previous(); + if (k < PERIOD) continue; + // check repeated pattern + int[] periodMove = pastMove[k - PERIOD]; + int deltaX = periodMove[0] - move[0], deltaY = periodMove[1] - move[1]; + if (k == PERIOD) { // first periodical move + dx = deltaX; + dy = deltaY; + } + boolean isRepeated = (deltaX == dx && deltaY == dy); + boolean isDiagonal = (Math.abs(deltaX) == 1 && Math.abs(deltaY) == 1); + if (!isRepeated || !isDiagonal) return false; + } + int[] myPeriodMove = pastMove[PERIOD - 1]; + int x = myPeriodMove[0] + dx, y = myPeriodMove[1] + dy; + boolean continued = isValidEmpty(x, y) && isValidEmpty(x + dx, y) && isValidEmpty(x, y + dy); + if (!continued) return false; + place(x, y); + return true; + } + /** for handicap */ public void flatten() { Stone[] stones = history.getStones(); @@ -1642,6 +1683,10 @@ public boolean isCoordsEmpty(int x, int y) { return true; } + private boolean isValidEmpty(int x, int y) { + return isValid(x, y) && isCoordsEmpty(x, y); + } + public boolean setAsMainBranch() { if (history.getCurrentHistoryNode().isMainTrunk()) return false; BoardHistoryNode topNode = history.getCurrentHistoryNode().topOfFatherBranch(); diff --git a/src/main/resources/l10n/DisplayStrings.properties b/src/main/resources/l10n/DisplayStrings.properties index 04dfb56ff..4fc07f479 100644 --- a/src/main/resources/l10n/DisplayStrings.properties +++ b/src/main/resources/l10n/DisplayStrings.properties @@ -262,6 +262,8 @@ Menu.game.continueGameWhite=Continue(Play white) Menu.game.breakGame=Interrupt game(Space) Menu.game.setInfo=Set gameInfo(I) Menu.game.bestOne=Play best move(,) +Menu.game.continueLadder=Continue ladder +Menu.game.continueLadderFail=Play at least %d ladder moves successively before auto-continuation. Menu.game.pass=Play pass(P) Menu.game.clearBoard=Clear board(Ctrl+Home) Menu.game.backToMain=Back to mainTrunk