From 922b8acf9610940f363147d9f233dfd7eac99f90 Mon Sep 17 00:00:00 2001 From: SudokuMonster Date: Sun, 13 Oct 2019 01:08:43 +0100 Subject: [PATCH 1/3] New Turbot and enhancements --- diuf/sudoku/Cell.java | 15 ++ diuf/sudoku/Grid.java | 10 +- diuf/sudoku/Settings.java | 4 +- diuf/sudoku/SolvingTechnique.java | 1 + diuf/sudoku/gui/GenerateDialog.java | 81 +++++++ diuf/sudoku/gui/TwoStringKite.html | 6 + diuf/sudoku/solver/Rule.java | 2 + diuf/sudoku/solver/Solver.java | 2 + diuf/sudoku/solver/rules/TurbotFish.java | 130 +++++++++++ diuf/sudoku/solver/rules/TurbotFishHint.html | 20 ++ diuf/sudoku/solver/rules/TurbotFishHint.java | 217 +++++++++++++++++++ diuf/sudoku/solver/rules/VWXYZWing.java | 19 +- diuf/sudoku/solver/rules/WXYZWing.java | 15 +- 13 files changed, 495 insertions(+), 27 deletions(-) create mode 100644 diuf/sudoku/gui/TwoStringKite.html create mode 100644 diuf/sudoku/solver/rules/TurbotFish.java create mode 100644 diuf/sudoku/solver/rules/TurbotFishHint.html create mode 100644 diuf/sudoku/solver/rules/TurbotFishHint.java diff --git a/diuf/sudoku/Cell.java b/diuf/sudoku/Cell.java index 11a800d..8fa5dd8 100644 --- a/diuf/sudoku/Cell.java +++ b/diuf/sudoku/Cell.java @@ -92,6 +92,21 @@ public CellSet getVisibleCells() { return Grid.visibleCellsSet[index]; } + public boolean canSeeCell(Cell other) { + return Grid.visibleCellsSet[index].contains(other); + } + + public boolean canSeeAnyOfCells(CellSet cellSet) { + CellSet currentSet = new CellSet(Grid.visibleCellsSet[index]); + currentSet.retainAll(cellSet); + int currentSetSize = currentSet.size(); + if (currentSetSize > 0) + return true; + return false; + } + + + /** * Get the cells that form the "house" of this cell. The * cell indexes have to be greater than this cell index. The "house" diff --git a/diuf/sudoku/Grid.java b/diuf/sudoku/Grid.java index 21b0400..c6b97a4 100644 --- a/diuf/sudoku/Grid.java +++ b/diuf/sudoku/Grid.java @@ -77,9 +77,9 @@ public class Grid { public static final int[][] cellRegions; public static final int[][] visibleCellIndex; public static final int[][] forwardVisibleCellIndex; - private static final Block[] blocks; - private static final Row[] rows; - private static final Column[] columns; + public static final Block[] blocks; + public static final Row[] rows; + public static final Column[] columns; public static final Region[][] regions; public static final CellSet[] visibleCellsSet; public static final CellSet[] forwardVisibleCellsSet; @@ -670,7 +670,7 @@ public String toString() { public String toFullString() { Settings settings = Settings.getInstance(); if (settings.isRCNotation()) - return toString() + " R" + (rowNum + 1); + return toString() + " " + (rowNum + 1); else return toString() + " " + (rowNum + 1); } @@ -714,7 +714,7 @@ public String toString() { public String toFullString() { Settings settings = Settings.getInstance(); if (settings.isRCNotation()) - return toString() + " C" + (columnNum + 1); + return toString() + " " + (columnNum + 1); else return toString() + " " + (char)('A' + columnNum); } diff --git a/diuf/sudoku/Settings.java b/diuf/sudoku/Settings.java index 95ec1f9..18ab5e7 100644 --- a/diuf/sudoku/Settings.java +++ b/diuf/sudoku/Settings.java @@ -17,9 +17,9 @@ public class Settings { public final static int VERSION = 1; - public final static int REVISION = 4; + public final static int REVISION = 5; public final static String SUBREV = ".1"; - public final static String releaseDate = "2019-09-22"; + public final static String releaseDate = "2019-10-13"; public final static String releaseYear = "2019"; public final static String releaseLicence = "Lesser General Public License"; public final static String releaseLicenceMini = "LGPL"; diff --git a/diuf/sudoku/SolvingTechnique.java b/diuf/sudoku/SolvingTechnique.java index ee98583..0c75ad0 100644 --- a/diuf/sudoku/SolvingTechnique.java +++ b/diuf/sudoku/SolvingTechnique.java @@ -19,6 +19,7 @@ public enum SolvingTechnique { NakedTriplet("Naked Triplet"), Swordfish("Swordfish"), HiddenTriplet("Hidden Triplet"), + TurbotFish("Scraper, Kite, Turbot"), XYWing("XY-Wing"), XYZWing("XYZ-Wing"), // WWing("W-Wing"), diff --git a/diuf/sudoku/gui/GenerateDialog.java b/diuf/sudoku/gui/GenerateDialog.java index 223359f..d559064 100644 --- a/diuf/sudoku/gui/GenerateDialog.java +++ b/diuf/sudoku/gui/GenerateDialog.java @@ -527,6 +527,87 @@ public String getnotMaxTechnique2() { public String getnotMaxTechnique3() { return ""; } + }, + TwoStringKite { + + @Override + public double getMinDifficulty() { + return 4.1; + } + + @Override + public double getMaxDifficulty() { + return 4.1; + } + @Override + public double getincludeDifficulty1() { + return 0.0; + } + @Override + public double getincludeDifficulty2() { + return 0.0; + } + @Override + public double getincludeDifficulty3() { + return 0.0; + } + @Override + public double getexcludeDifficulty1() { + return 0.0; + } + @Override + public double getexcludeDifficulty2() { + return 0.0; + } + @Override + public double getexcludeDifficulty3() { + return 0.0; + } + @Override + public double getnotMaxDifficulty1() { + return 0.0; + } + @Override + public double getnotMaxDifficulty2() { + return 0.0; + } + @Override + public double getnotMaxDifficulty3() { + return 0.0; + } + public String getexcludeTechnique1() { + return "Skyscraper"; + } + @Override + public String getexcludeTechnique2() { + return ""; + } + @Override + public String getexcludeTechnique3() { + return ""; + } + public String getincludeTechnique1() { + return ""; + } + @Override + public String getincludeTechnique2() { + return ""; + } + @Override + public String getincludeTechnique3() { + return ""; + } + public String getnotMaxTechnique1() { + return ""; + } + @Override + public String getnotMaxTechnique2() { + return ""; + } + @Override + public String getnotMaxTechnique3() { + return ""; + } }, WXYZ { diff --git a/diuf/sudoku/gui/TwoStringKite.html b/diuf/sudoku/gui/TwoStringKite.html new file mode 100644 index 0000000..5c80500 --- /dev/null +++ b/diuf/sudoku/gui/TwoStringKite.html @@ -0,0 +1,6 @@ + + + 2-String Kite: The Sudoku requires a 2-String Kite to solve. + Rating: 4.1 + + \ No newline at end of file diff --git a/diuf/sudoku/solver/Rule.java b/diuf/sudoku/solver/Rule.java index 29a355c..c56e8c3 100644 --- a/diuf/sudoku/solver/Rule.java +++ b/diuf/sudoku/solver/Rule.java @@ -44,6 +44,7 @@ public interface Rule { *
  • 2.8: Claiming *
  • 3.0, 3.2, 3.4: Naked pair, X-Wing, Hidden pair *
  • 3.6, 3.8, 4.0: Naked triplet, Swordfish, Hidden triplet + *
  • 4.0, 4.1, 4.2: Skyscraper, 2-String Kite, Turbot Fish *
  • 4.2, 4.4: XY-Wing, XYZ-Wing // *
  • 4.4: W-Wing *
  • 4.5 - 5.0: Unique Rectangles and Loops @@ -84,6 +85,7 @@ public interface Rule { *
  • 3.1: Direct Hidden Triplet//2.5 ---> 3.0 ---> 3.1 *
  • 3.2: X-Wing *
  • 3.6, 3.8, 4.0: Naked triplet, Hidden triplet, Swordfish//3.8 ---> 4.0 4.0 ---> 3.8 + *
  • 4.0, 4.1, 4.2: Skyscraper, 2-String Kite, Turbot Fish (placed before Swordfish) *
  • 4.2, 4.4: XY-Wing, XYZ-Wing // *
  • 4.4: W-Wing *
  • 4.5 - 5.0: Unique Rectangles and Loops diff --git a/diuf/sudoku/solver/Solver.java b/diuf/sudoku/solver/Solver.java index ae43318..a531a4c 100644 --- a/diuf/sudoku/solver/Solver.java +++ b/diuf/sudoku/solver/Solver.java @@ -148,6 +148,7 @@ public Solver(Grid grid) { addIfWorth(SolvingTechnique.XWing, indirectHintProducers, new Fisherman(2)); addIfWorth(SolvingTechnique.NakedTriplet, indirectHintProducers, new NakedSet(3)); addIfWorth(SolvingTechnique.HiddenTriplet, indirectHintProducers, new HiddenSet(3, false)); + addIfWorth(SolvingTechnique.TurbotFish, indirectHintProducers, new TurbotFish()); addIfWorth(SolvingTechnique.Swordfish, indirectHintProducers, new Fisherman(3)); addIfWorth(SolvingTechnique.XYWing, indirectHintProducers, new XYWing(false)); addIfWorth(SolvingTechnique.XYZWing, indirectHintProducers, new XYWing(true)); @@ -202,6 +203,7 @@ public Solver(Grid grid) { addIfWorth(SolvingTechnique.NakedTriplet, indirectHintProducers, new NakedSet(3)); addIfWorth(SolvingTechnique.Swordfish, indirectHintProducers, new Fisherman(3)); addIfWorth(SolvingTechnique.HiddenTriplet, indirectHintProducers, new HiddenSet(3, false)); + addIfWorth(SolvingTechnique.TurbotFish, indirectHintProducers, new TurbotFish()); addIfWorth(SolvingTechnique.XYWing, indirectHintProducers, new XYWing(false)); addIfWorth(SolvingTechnique.XYZWing, indirectHintProducers, new XYWing(true)); // addIfWorth(SolvingTechnique.WWing, indirectHintProducers, new WWing()); diff --git a/diuf/sudoku/solver/rules/TurbotFish.java b/diuf/sudoku/solver/rules/TurbotFish.java new file mode 100644 index 0000000..99a449e --- /dev/null +++ b/diuf/sudoku/solver/rules/TurbotFish.java @@ -0,0 +1,130 @@ +package diuf.sudoku.solver.rules; + +import java.util.*; +import diuf.sudoku.*; +import diuf.sudoku.solver.*; +import diuf.sudoku.tools.*; + + +/** + * Implementation of Turbot Fish technique solver. + */ +public class TurbotFish implements IndirectHintProducer { + + @Override + public void getHints(Grid grid, HintsAccumulator accu) throws InterruptedException { + // Skyscrapers + getHints(grid, accu, 1, 1); + getHints(grid, accu, 2, 2); + // Two-string Kites + getHints(grid, accu, 2, 1); + //getHints(grid, accu, 1, 2);The same + // Turbot Fishes + getHints(grid, accu, 1, 0); + getHints(grid, accu, 2, 0); + //getHints(grid, accu, 0, 1);The same + //getHints(grid, accu, 0, 2);The same + // Generalized X-Wing... + //getHints(grid, accu, 0, 0); + } + + private Grid.Region shareRegionOf(Grid grid, + Cell start, Cell bridge1, Cell bridge2, Cell end) { + if (bridge1.getX() == bridge2.getX()) { + return Grid.columns[bridge1.getX()]; + } else if (bridge1.getY() == bridge2.getY()) { + return Grid.rows[bridge1.getY()]; + } else if (bridge1.getB() == bridge2.getB()) { + return Grid.blocks[bridge1.getB()]; + } else return null; + } + + private void getHints(Grid grid, HintsAccumulator accu, + int base, int cover) + throws InterruptedException { + for (int digit = 1; digit <= 9; digit++) { + Grid.Region[] baseRegions = grid.getRegions(base); + Grid.Region[] coverRegions = grid.getRegions(cover); + for (int i1 = 0; i1 < baseRegions.length; i1++) { + for (int i2 = (base == cover ? i1+1 : 0); i2 < coverRegions.length; i2++) { + // For each set in sets + Grid.Region baseRegion = baseRegions[i1]; + Grid.Region coverRegion = coverRegions[i2]; + + // Same region, skip + //if (baseRegion.getClass() == coverRegion.getClass() && i1 == i2) + //continue; + BitSet baseRegionPotentials = baseRegion.getPotentialPositions(grid, digit); + BitSet coverRegionPotentials = coverRegion.getPotentialPositions(grid, digit); + if (baseRegionPotentials.cardinality() == 2 && coverRegionPotentials.cardinality() == 2) { + // Strong links found (Conjugate pairs found) + // Check whether positions may in the same region or not (form a weak link) + int p1, p2; + Cell[] cells = new Cell[] { + // region 1 + baseRegion.getCell(p1 = baseRegionPotentials.nextSetBit(0)), + baseRegion.getCell(baseRegionPotentials.nextSetBit(p1 + 1)), + // region 2 + coverRegion.getCell(p2 = coverRegionPotentials.nextSetBit(0)), + coverRegion.getCell(coverRegionPotentials.nextSetBit(p2 + 1)) + }; + + // Cells cannot be same + boolean next = false; + for (int i = 0; i < 3; i++) { + for (int j = i + 1; j < 4; j++) { + if (cells[i].equals(cells[j])) { + next = true; + break; + } + } + } + if (next) continue; + + Grid.Region shareRegion; + Cell start, end, bridge1, bridge2; + for (int i = 0; i < 2; i++) { + for (int j = 2; j < 4; j++) { + if ((shareRegion = shareRegionOf(grid, + start = cells[i], + bridge1 = cells[1 - i], + bridge2 = cells[j], + end = cells[5 - j])) != null && + !shareRegion.equals(baseRegion) && !shareRegion.equals(coverRegion)) { + // Turbot fish found + TurbotFishHint hint = createHint(grid, digit, start, end, bridge1, bridge2, + baseRegion, coverRegion, shareRegion); + if (hint.isWorth()) + accu.add(hint); + } + } // for int j = 0..2 + } // for int i = 0..2 + } // if baseRegionPotentials.cardinality() == 2 && coverRegionPotentials.cardinality() == 2 + } + } + } + } + + private TurbotFishHint createHint(Grid grid, int value, Cell start, Cell end, Cell bridgeCell1, Cell bridgeCell2, + Grid.Region baseSet, Grid.Region coverSet, Grid.Region shareRegion) { + // Build list of removable potentials + Map removablePotentials = new HashMap<>(); + Set victims = new LinkedHashSet<>(start.getVisibleCells()); + victims.retainAll(end.getVisibleCells()); + victims.remove(start); + victims.remove(end); + for (Cell cell : victims) { + if (grid.hasCellPotentialValue(cell.getIndex(), value)) + removablePotentials.put(cell, SingletonBitSet.create(value)); + } + + // Create hint + return new TurbotFishHint(this, removablePotentials, + start, end, bridgeCell1, bridgeCell2, value, baseSet, coverSet, shareRegion); + } + + @Override + public String toString() { + return "Turbot Fishes"; + } +} diff --git a/diuf/sudoku/solver/rules/TurbotFishHint.html b/diuf/sudoku/solver/rules/TurbotFishHint.html new file mode 100644 index 0000000..0330759 --- /dev/null +++ b/diuf/sudoku/solver/rules/TurbotFishHint.html @@ -0,0 +1,20 @@ + + +

    {0}

    +

    + This technique relies on Conjugate Pairs. + The value {1} in {6} and {7} forms + 2 strong links (conjugate pairs). The bridge cells + {3} and {4} share the same region ({8}), + therefore forming a weak link. +

    +

    + Any occurrence of the value {1} can therefore be + removed from any cells sharing a row, column or block + with all with both start cells {2} and {5}. +

    +

    + This technique is similar to {9} technique logic. +

    + + \ No newline at end of file diff --git a/diuf/sudoku/solver/rules/TurbotFishHint.java b/diuf/sudoku/solver/rules/TurbotFishHint.java new file mode 100644 index 0000000..c1820d7 --- /dev/null +++ b/diuf/sudoku/solver/rules/TurbotFishHint.java @@ -0,0 +1,217 @@ +package diuf.sudoku.solver.rules; + +import java.util.*; +import diuf.sudoku.*; +import diuf.sudoku.solver.*; +import diuf.sudoku.solver.rules.chaining.*; +import diuf.sudoku.tools.*; + + +/** + * Turbot Fish hints + */ +public class TurbotFishHint extends IndirectHint implements Rule, HasParentPotentialHint { + + private final int value; + private final Cell startCell; + private final Cell endCell; + private final Cell bridgeCell1; + private final Cell bridgeCell2; + private final Grid.Region baseSet; + private final Grid.Region coverSet; + private final Grid.Region shareRegion; + + public TurbotFishHint(IndirectHintProducer rule, Map removablePotentials, + Cell startCell, Cell endCell, Cell bridgeCell1, Cell bridgeCell2, + int value, Grid.Region base, Grid.Region cover, Grid.Region shareRegion) { + super(rule, removablePotentials); + this.value = value; + this.startCell = startCell; + this.endCell = endCell; + this.bridgeCell1 = bridgeCell1; + this.bridgeCell2 = bridgeCell2; + this.baseSet = base; + this.coverSet = cover; + this.shareRegion = shareRegion; + } + + @Override + public int getViewCount() { + return 1; + } + + @Override + public Cell[] getSelectedCells() { + return new Cell[] { startCell, endCell }; + } + + @Override + public Map getGreenPotentials(Grid grid, int viewNum) { + Map result = new HashMap<>(); + BitSet fishDigitSet = SingletonBitSet.create(value); + result.put(startCell, fishDigitSet); // orange + result.put(bridgeCell1, fishDigitSet); + result.put(bridgeCell2, fishDigitSet); // orange + result.put(endCell, fishDigitSet); + return result; + } + + @Override + public Map getRedPotentials(Grid grid, int viewNum) { + Map result = new HashMap<>(super.getRemovablePotentials()); + BitSet fishDigitSet = SingletonBitSet.create(value); + result.put(startCell, fishDigitSet); + result.put(bridgeCell2, fishDigitSet); + return result; + } + + @Override + public Collection getLinks(Grid grid, int viewNum) { + Collection result = new ArrayList<>(); + result.add(new Link(startCell, value, bridgeCell1, value)); + result.add(new Link(bridgeCell1, value, bridgeCell2, value)); + result.add(new Link(bridgeCell2, value, endCell, value)); + return result; + } + + @Override + public Grid.Region[] getRegions() { + //return new Grid.Region[] { shareRegion }; + return null; + } + + @Override + public String toString() { + return getName() + + ": " + + Cell.toFullString(startCell, bridgeCell1, bridgeCell2, endCell) + + " on value " + + value; + } + + @Override + public String toHtml(Grid grid) { + String result = HtmlLoader.loadHtml(this, "TurbotFishHint.html"); + String name = getName(); + String base = this.baseSet.toFullString(); + String cover = this.coverSet.toFullString(); + String shared = this.shareRegion.toFullString(); + String value = Integer.toString(this.value); + String cell1 = startCell.toString(); + String cell2 = bridgeCell1.toString(); + String cell3 = bridgeCell2.toString(); + String cell4 = endCell.toString(); + String finned = "finned mutant sashimi x-wing"; + if (name == "Skyscraper") + finned = "finned sashimi x-wing"; + else + if (name == "Turbot Fish") + finned = "finned franken sashimi x-wing"; + result = HtmlLoader.format(result, name, value, cell1, cell2, cell3, cell4, base, cover, shared, finned); + return result; + } + + @Override + public String getName() { + Class region1 = baseSet.getClass(); + Class region2 = coverSet.getClass(); + if (region1 == Grid.Row.class) { + if (region2 == Grid.Row.class) + return "Skyscraper"; + else + if (region2 == Grid.Column.class) + return "Two-string Kite"; + else + return "Turbot Fish"; + } + else { + if (region1 == Grid.Column.class) + if (region2 == Grid.Row.class) + return "Two-string Kite"; + else + if (region2 == Grid.Column.class) + return "Skyscraper"; + else + return "Turbot Fish"; + else + return "Turbot Fish"; + } + } + + @Override + public String getShortName() { + Class region1 = baseSet.getClass(); + Class region2 = coverSet.getClass(); + if (region1 == Grid.Row.class) { + if (region2 == Grid.Row.class) + return "Sky"; + else + if (region2 == Grid.Column.class) + return "2SK"; + else + return "TF"; + } + else { + if (region1 == Grid.Column.class) + if (region2 == Grid.Row.class) + return "2SK"; + else + if (region2 == Grid.Column.class) + return "Sky"; + else + return "TF"; + else + if (region2 == Grid.Block.class) + return "GXW"; + else + return "TF"; + } + } + + @Override + public double getDifficulty() { + String name = getName(); + if (name.equals("Skyscraper")) { + return 4.0; + } else if (name.equals("Two-string Kite")) { + return 4.1; + } else { + return 4.2; + } + } + + @Override + public int hashCode() { + return startCell.hashCode() ^ endCell.hashCode() ^ + bridgeCell1.hashCode() ^ bridgeCell2.hashCode() ^ value; + } + + + public String getClueHtml(Grid grid, boolean isBig) { + if (isBig) { + return "Look for a " + getName() + " on the value " + value; + } else { + return "Look for a " + getName(); + } + } + + + + @Override + public Collection getRuleParents(Grid initialGrid, Grid currentGrid) { + Collection result = new ArrayList<>(); + Cell startCell = Grid.getCell(this.startCell.getIndex()); + Cell endCell = Grid.getCell(this.endCell.getIndex()); + Cell bridgeCell1 = Grid.getCell(this.bridgeCell1.getIndex()); + Cell bridgeCell2 = Grid.getCell(this.bridgeCell2.getIndex()); + if (initialGrid.hasCellPotentialValue(startCell.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.startCell.getIndex(), value)) + result.add(new Potential(this.startCell, value, false)); + if (initialGrid.hasCellPotentialValue(bridgeCell1.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.bridgeCell1.getIndex(), value)) + result.add(new Potential(this.bridgeCell1, value, false)); + if (initialGrid.hasCellPotentialValue(bridgeCell2.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.bridgeCell2.getIndex(), value)) + result.add(new Potential(this.bridgeCell2, value, false)); + if (initialGrid.hasCellPotentialValue(endCell.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.endCell.getIndex(), value)) + result.add(new Potential(this.endCell, value, false)); + return result; + } +} diff --git a/diuf/sudoku/solver/rules/VWXYZWing.java b/diuf/sudoku/solver/rules/VWXYZWing.java index 21cc29c..b4855e5 100644 --- a/diuf/sudoku/solver/rules/VWXYZWing.java +++ b/diuf/sudoku/solver/rules/VWXYZWing.java @@ -25,19 +25,19 @@ public class VWXYZWing implements IndirectHintProducer { private boolean isVWXYZWing(BitSet vwxyzValues,BitSet vzValues, BitSet wzValues, BitSet xzValues, BitSet aBit, Cell yzCell, Cell xzCell, Cell wzCell, Cell vzCell, Cell vwxyzCell) { BitSet inter = (BitSet)aBit.clone(); inter.and(xzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == xzCell.getX() || yzCell.getY() == xzCell.getY() || yzCell.getB() == xzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(xzCell)) return false; inter = (BitSet)aBit.clone(); inter.and(wzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == wzCell.getX() || yzCell.getY() == wzCell.getY() || yzCell.getB() == wzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(wzCell)) return false; inter = (BitSet)aBit.clone(); inter.and(vzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == vzCell.getX() || yzCell.getY() == vzCell.getY() || yzCell.getB() == vzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(vzCell)) return false; inter = (BitSet)aBit.clone(); inter.and(vwxyzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == vwxyzCell.getX() || yzCell.getY() == vwxyzCell.getY() || yzCell.getB() == vwxyzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(vwxyzCell)) return false; return true; } @@ -103,13 +103,10 @@ public void getHints(Grid grid, HintsAccumulator accu) throws InterruptedExcepti biggestCardinality4 = xzValues.cardinality(); wingSize = vwxyzValues.cardinality() + vzValues.cardinality() + wzValues.cardinality() + xzValues.cardinality(); //Restrict potential yzCell to Grid Cells that are visible by one or more of the other cells - CellSet noYZ = new CellSet(new int[]{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,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80}); - noYZ.removeAll(vwxyzCell.getVisibleCells()); - noYZ.removeAll(vzCell.getVisibleCells()); - noYZ.removeAll(wzCell.getVisibleCells()); - noYZ.removeAll(xzCell.getVisibleCells()); - CellSet yzCellRange = new CellSet(new int[]{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,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80}); - yzCellRange.removeAll(noYZ); + CellSet yzCellRange = new CellSet(vwxyzCell.getVisibleCells()); + yzCellRange.addAll(vzCell.getVisibleCells()); + yzCellRange.addAll(wzCell.getVisibleCells()); + yzCellRange.addAll(xzCell.getVisibleCells()); yzCellRange.remove(vwxyzCell); yzCellRange.remove(vzCell); yzCellRange.remove(wzCell); diff --git a/diuf/sudoku/solver/rules/WXYZWing.java b/diuf/sudoku/solver/rules/WXYZWing.java index 7ea8188..47d4082 100644 --- a/diuf/sudoku/solver/rules/WXYZWing.java +++ b/diuf/sudoku/solver/rules/WXYZWing.java @@ -25,15 +25,15 @@ public class WXYZWing implements IndirectHintProducer { private boolean isWXYZWing(BitSet wxyzValues,BitSet wzValues, BitSet xzValues, BitSet aBit, Cell yzCell, Cell xzCell, Cell wzCell, Cell wxyzCell) { BitSet inter = (BitSet)aBit.clone(); inter.and(xzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == xzCell.getX() || yzCell.getY() == xzCell.getY() || yzCell.getB() == xzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(xzCell)) return false; inter = (BitSet)aBit.clone(); inter.and(wzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == wzCell.getX() || yzCell.getY() == wzCell.getY() || yzCell.getB() == wzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(wzCell)) return false; inter = (BitSet)aBit.clone(); inter.and(wxyzValues); - if (inter.cardinality() == 1 && !(yzCell.getX() == wxyzCell.getX() || yzCell.getY() == wxyzCell.getY() || yzCell.getB() == wxyzCell.getB())) + if (inter.cardinality() == 1 && !yzCell.canSeeCell(wxyzCell)) return false; return true; } @@ -81,12 +81,9 @@ public void getHints(Grid grid, HintsAccumulator accu) throws InterruptedExcepti biggestCardinality3 = xzValues.cardinality(); wingSize = wxyzValues.cardinality() + wzValues.cardinality() + xzValues.cardinality(); //Restrict potential yzCell to Grid Cells that are visible by one or more of the other cells - CellSet noYZ = new CellSet(new int[]{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,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80}); - noYZ.removeAll(wxyzCell.getVisibleCells()); - noYZ.removeAll(wzCell.getVisibleCells()); - noYZ.removeAll(xzCell.getVisibleCells()); - CellSet yzCellRange = new CellSet(new int[]{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,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80}); - yzCellRange.removeAll(noYZ); + CellSet yzCellRange = new CellSet(wxyzCell.getVisibleCells()); + yzCellRange.addAll(wzCell.getVisibleCells()); + yzCellRange.addAll(xzCell.getVisibleCells()); yzCellRange.remove(wxyzCell); yzCellRange.remove(wzCell); yzCellRange.remove(xzCell); From f4a236d75e751a9b4b6cdd7198d31566e466c30e Mon Sep 17 00:00:00 2001 From: SudokuMonster Date: Mon, 14 Oct 2019 21:07:29 +0100 Subject: [PATCH 2/3] 3 Strong links technique --- diuf/sudoku/Settings.java | 6 +- diuf/sudoku/SolvingTechnique.java | 1 + diuf/sudoku/gui/GenerateDialog.java | 81 ++++ diuf/sudoku/gui/ThreeStrongLinks.html | 6 + diuf/sudoku/solver/Rule.java | 2 + diuf/sudoku/solver/Solver.java | 3 +- .../sudoku/solver/rules/ThreeStrongLinks.java | 207 +++++++++ .../solver/rules/ThreeStrongLinksHint.html | 23 + .../solver/rules/ThreeStrongLinksHint.java | 395 ++++++++++++++++++ diuf/sudoku/solver/rules/TurbotFishHint.html | 4 +- diuf/sudoku/solver/rules/TurbotFishHint.java | 8 +- 11 files changed, 723 insertions(+), 13 deletions(-) create mode 100644 diuf/sudoku/gui/ThreeStrongLinks.html create mode 100644 diuf/sudoku/solver/rules/ThreeStrongLinks.java create mode 100644 diuf/sudoku/solver/rules/ThreeStrongLinksHint.html create mode 100644 diuf/sudoku/solver/rules/ThreeStrongLinksHint.java diff --git a/diuf/sudoku/Settings.java b/diuf/sudoku/Settings.java index 18ab5e7..e24bc81 100644 --- a/diuf/sudoku/Settings.java +++ b/diuf/sudoku/Settings.java @@ -17,9 +17,9 @@ public class Settings { public final static int VERSION = 1; - public final static int REVISION = 5; - public final static String SUBREV = ".1"; - public final static String releaseDate = "2019-10-13"; + public final static int REVISION = 6; + public final static String SUBREV = ".0"; + public final static String releaseDate = "2019-10-14"; public final static String releaseYear = "2019"; public final static String releaseLicence = "Lesser General Public License"; public final static String releaseLicenceMini = "LGPL"; diff --git a/diuf/sudoku/SolvingTechnique.java b/diuf/sudoku/SolvingTechnique.java index 0c75ad0..0deca20 100644 --- a/diuf/sudoku/SolvingTechnique.java +++ b/diuf/sudoku/SolvingTechnique.java @@ -28,6 +28,7 @@ public enum SolvingTechnique { NakedQuad("Naked Quad"), Jellyfish("Jellyfish"), HiddenQuad("Hidden Quad"), + ThreeStrongLinks("3 Strong-linked Fishes"), //VWXYZWing4("VWXYZ-Wing 4"), //VWXYZWing5("VWXYZ-Wing 5"), VWXYZWing("VWXYZ-Wing"), diff --git a/diuf/sudoku/gui/GenerateDialog.java b/diuf/sudoku/gui/GenerateDialog.java index d559064..e710dce 100644 --- a/diuf/sudoku/gui/GenerateDialog.java +++ b/diuf/sudoku/gui/GenerateDialog.java @@ -608,6 +608,87 @@ public String getnotMaxTechnique2() { public String getnotMaxTechnique3() { return ""; } + }, + ThreeStrongLinks { + + @Override + public double getMinDifficulty() { + return 5.4; + } + + @Override + public double getMaxDifficulty() { + return 5.6; + } + @Override + public double getincludeDifficulty1() { + return 0.0; + } + @Override + public double getincludeDifficulty2() { + return 0.0; + } + @Override + public double getincludeDifficulty3() { + return 0.0; + } + @Override + public double getexcludeDifficulty1() { + return 0.0; + } + @Override + public double getexcludeDifficulty2() { + return 0.0; + } + @Override + public double getexcludeDifficulty3() { + return 0.0; + } + @Override + public double getnotMaxDifficulty1() { + return 0.0; + } + @Override + public double getnotMaxDifficulty2() { + return 0.0; + } + @Override + public double getnotMaxDifficulty3() { + return 0.0; + } + public String getexcludeTechnique1() { + return ""; + } + @Override + public String getexcludeTechnique2() { + return ""; + } + @Override + public String getexcludeTechnique3() { + return ""; + } + public String getincludeTechnique1() { + return " 10"; + } + @Override + public String getincludeTechnique2() { + return ""; + } + @Override + public String getincludeTechnique3() { + return ""; + } + public String getnotMaxTechnique1() { + return ""; + } + @Override + public String getnotMaxTechnique2() { + return ""; + } + @Override + public String getnotMaxTechnique3() { + return ""; + } }, WXYZ { diff --git a/diuf/sudoku/gui/ThreeStrongLinks.html b/diuf/sudoku/gui/ThreeStrongLinks.html new file mode 100644 index 0000000..edf0f11 --- /dev/null +++ b/diuf/sudoku/gui/ThreeStrongLinks.html @@ -0,0 +1,6 @@ + + + 3 strong links: Requires a 3 Strong-link fish to solve in 102 formation + Rating: 5.6 + + \ No newline at end of file diff --git a/diuf/sudoku/solver/Rule.java b/diuf/sudoku/solver/Rule.java index c56e8c3..011ed1e 100644 --- a/diuf/sudoku/solver/Rule.java +++ b/diuf/sudoku/solver/Rule.java @@ -49,6 +49,7 @@ public interface Rule { // *
  • 4.4: W-Wing *
  • 4.5 - 5.0: Unique Rectangles and Loops *
  • 5.0, 5.2, 5.4: Naked quad, Jellyfish, Hidden quad + *
  • 5.4, 5.5, 5.6: Skyscraper (3SL) , 3-String Kite, Turbot Fish (3SL) *
  • 5.5: WXYZ-Wing *
  • 5.6 - 6.0: Bivalue Universal Graves *
  • 6.2 - 6.4: VWXYZ-Wing @@ -90,6 +91,7 @@ public interface Rule { // *
  • 4.4: W-Wing *
  • 4.5 - 5.0: Unique Rectangles and Loops *
  • 5.0, 5.2, 5.4: Naked quad, Hidden quad, Jellyfish//5.2 ---> 5.4 5.4 ---> 5.2 + *
  • 5.4, 5.5, 5.6: Skyscraper (3SL) , 3-String Kite, Turbot Fish (3SL) *
  • 5.5: WXYZ-Wing *
  • 5.6 - 6.0: Bivalue Universal Graves *
  • 6.2 - 6.4: VWXYZ-Wing diff --git a/diuf/sudoku/solver/Solver.java b/diuf/sudoku/solver/Solver.java index a531a4c..9b565f4 100644 --- a/diuf/sudoku/solver/Solver.java +++ b/diuf/sudoku/solver/Solver.java @@ -211,7 +211,8 @@ public Solver(Grid grid) { addIfWorth(SolvingTechnique.NakedQuad, indirectHintProducers, new NakedSet(4)); addIfWorth(SolvingTechnique.Jellyfish, indirectHintProducers, new Fisherman(4)); addIfWorth(SolvingTechnique.HiddenQuad, indirectHintProducers, new HiddenSet(4, false)); - addIfWorth(SolvingTechnique.WXYZWing, indirectHintProducers, new WXYZWing()); + addIfWorth(SolvingTechnique.ThreeStrongLinks, indirectHintProducers, new ThreeStrongLinks()); + addIfWorth(SolvingTechnique.WXYZWing, indirectHintProducers, new WXYZWing()); //addIfWorth(SolvingTechnique.VWXYZWing4, indirectHintProducers, new VWXYZWing(true)); //addIfWorth(SolvingTechnique.VWXYZWing5, indirectHintProducers, new VWXYZWing(false)); addIfWorth(SolvingTechnique.BivalueUniversalGrave, indirectHintProducers, new BivalueUniversalGrave()); diff --git a/diuf/sudoku/solver/rules/ThreeStrongLinks.java b/diuf/sudoku/solver/rules/ThreeStrongLinks.java new file mode 100644 index 0000000..b287411 --- /dev/null +++ b/diuf/sudoku/solver/rules/ThreeStrongLinks.java @@ -0,0 +1,207 @@ +package diuf.sudoku.solver.rules; + +import java.util.*; +import diuf.sudoku.*; +import diuf.sudoku.solver.*; +import diuf.sudoku.tools.*; + + +/** + * Implementation of Turbot Fish technique solver. + */ +public class ThreeStrongLinks implements IndirectHintProducer { + + @Override + public void getHints(Grid grid, HintsAccumulator accu) throws InterruptedException { + // 3 parallel strong links + getHints(grid, accu, 1, 1, 1); + getHints(grid, accu, 2, 2, 2); + // 3 strong links in same Lines + //with boxes + getHints(grid, accu, 0, 0, 0); + getHints(grid, accu, 0, 0, 1); + getHints(grid, accu, 0, 0, 2); + getHints(grid, accu, 0, 1, 1); + getHints(grid, accu, 0, 2, 2); + // 3 strong links with box(es) + //mixed lines + getHints(grid, accu, 0, 1, 2); + getHints(grid, accu, 1, 1, 2); + getHints(grid, accu, 1, 2, 2); + } + + private Grid.Region shareRegionOf(Grid grid, + Cell start, Cell bridge1, Cell bridge2, Cell end) { + if (bridge1.getX() == bridge2.getX()) { + return Grid.columns[bridge1.getX()]; + } else if (bridge1.getY() == bridge2.getY()) { + return Grid.rows[bridge1.getY()]; + } else if (bridge1.getB() == bridge2.getB()) { + return Grid.blocks[bridge1.getB()]; + } else return null; + } + + private boolean isSamebox (Cell boxCell1, Cell boxCell2) { + if (boxCell1.getB() == boxCell2.getB()) + return true; + return false; + } + + private void getHints(Grid grid, HintsAccumulator accu, + int baseLink1, int baseLink2, int baseLink3) + throws InterruptedException { + Cell[] cells = new Cell[6]; + for (int digit = 1; digit <= 9; digit++) { + Grid.Region[] baseLink1Regions = grid.getRegions(baseLink1); + Grid.Region[] baseLink2Regions = grid.getRegions(baseLink2); + Grid.Region[] baseLink3Regions = grid.getRegions(baseLink3); + int p1, p2, p3; + for (int i1 = 0; i1 < baseLink1Regions.length; i1++) { + Grid.Region baseLink1Region = baseLink1Regions[i1]; + BitSet baseLink1RegionPotentials = baseLink1Region.getPotentialPositions(grid, digit); + if (baseLink1RegionPotentials.cardinality() != 2) + continue; + // region 1 + Cell regionCell1 = baseLink1Region.getCell(p1 = baseLink1RegionPotentials.nextSetBit(0)); + Cell regionCell2 = baseLink1Region.getCell(baseLink1RegionPotentials.nextSetBit(p1 + 1)); + if (baseLink1 > 0 && isSamebox(regionCell1,regionCell2)) + continue; + cells[0] = regionCell1; + cells[1] = regionCell2; + for (int i2 = (baseLink1 == baseLink2 ? i1+1 : 0); i2 < baseLink2Regions.length; i2++) { + Grid.Region baseLink2Region = baseLink2Regions[i2]; + BitSet baseLink2RegionPotentials = baseLink2Region.getPotentialPositions(grid, digit); + if (baseLink2RegionPotentials.cardinality() != 2) + continue; + // region 2 + regionCell1 = baseLink2Region.getCell(p2 = baseLink2RegionPotentials.nextSetBit(0)); + regionCell2 = baseLink2Region.getCell(baseLink2RegionPotentials.nextSetBit(p2 + 1)); + if (baseLink2 > 0 && isSamebox(regionCell1,regionCell2)) + continue; + cells[2] = regionCell1; + cells[3] = regionCell2; + for (int i3 = (baseLink2 == baseLink3 ? i2+1 : (baseLink1 == baseLink3 ? i1+1 : 0)); i3 < baseLink3Regions.length; i3++) { + Grid.Region baseLink3Region = baseLink3Regions[i3]; + BitSet baseLink3RegionPotentials = baseLink3Region.getPotentialPositions(grid, digit); + if (baseLink3RegionPotentials.cardinality() != 2) + continue; + regionCell1 = baseLink3Region.getCell(p3 = baseLink3RegionPotentials.nextSetBit(0)); + regionCell2 = baseLink3Region.getCell(baseLink3RegionPotentials.nextSetBit(p3 + 1)); + if (baseLink3 > 0 && isSamebox(regionCell1,regionCell2)) + continue; + cells[4] = regionCell1; + cells[5] = regionCell2; + boolean next = false; + for (int i = 0; i < 4; i++) { + for (int j = i + 1; j < 5; j++) { + for (int k = j + 1; k < 6; k++) { + if (cells[i].equals(cells[j]) || cells[i].equals(cells[k]) || cells[j].equals(cells[k])) { + next = true; + break; + } + } + } + } + if (next) { + cells[4] = null; + cells[5] = null; + continue; + } + Grid.Region shareRegion1, shareRegion2; + Cell start1, bridge11, bridge12, end2, bridge21, bridge22; + for (int i = 0; i < 2; i++) { + for (int j = 2; j < 4; j++) { + for (int k = 4; k < 6; k++) { + if ((shareRegion1 = shareRegionOf(grid, + start1 = cells[i], + bridge11 = cells[1 - i], + bridge12 = cells[j], + bridge21 = cells[5 - j])) != null && + (shareRegion2 = shareRegionOf(grid, + bridge12 = cells[j], + bridge21 = cells[5 - j], + bridge22 = cells[k], + end2 = cells[9 - k])) != null && + !shareRegion1.equals(baseLink1Region) && !shareRegion1.equals(baseLink2Region) && !shareRegion1.equals(baseLink3Region) && + !shareRegion2.equals(baseLink1Region) && !shareRegion2.equals(baseLink2Region) && !shareRegion2.equals(baseLink3Region) ) { + // 3 strong-linked Turbot fish found + ThreeStrongLinksHint hint = createHint(grid, digit, start1, bridge11, bridge12, + baseLink1Region, baseLink2Region, shareRegion1, end2, bridge21, bridge22, baseLink3Region, shareRegion2, baseLink1, baseLink2, baseLink3); + if (hint.isWorth()) + accu.add(hint); + } + if ((shareRegion1 = shareRegionOf(grid, + start1 = cells[i], + bridge11 = cells[1 - i], + bridge12 = cells[k], + bridge21 = cells[9 - k])) != null && + (shareRegion2 = shareRegionOf(grid, + bridge12 = cells[k], + bridge21 = cells[9 - k], + bridge22 = cells[j], + end2 = cells[5 - j])) != null && + !shareRegion1.equals(baseLink1Region) && !shareRegion1.equals(baseLink2Region) && !shareRegion1.equals(baseLink3Region) && + !shareRegion2.equals(baseLink1Region) && !shareRegion2.equals(baseLink2Region) && !shareRegion2.equals(baseLink3Region) ) { + // 3 strong-linked Turbot fish found + ThreeStrongLinksHint hint = createHint(grid, digit, start1, bridge11, bridge12, + baseLink1Region, baseLink3Region, shareRegion1, end2, bridge21, bridge22, baseLink2Region, shareRegion2, baseLink1, baseLink3, baseLink2); + if (hint.isWorth()) + accu.add(hint); + } + if ((shareRegion1 = shareRegionOf(grid, + start1 = cells[j], + bridge11 = cells[5 - j], + bridge12 = cells[i], + bridge21 = cells[1 - i])) != null && + (shareRegion2 = shareRegionOf(grid, + bridge12 = cells[i], + bridge21 = cells[1 - i], + bridge22 = cells[k], + end2 = cells[9 - k])) != null && + !shareRegion1.equals(baseLink1Region) && !shareRegion1.equals(baseLink2Region) && !shareRegion1.equals(baseLink3Region) && + !shareRegion2.equals(baseLink1Region) && !shareRegion2.equals(baseLink2Region) && !shareRegion2.equals(baseLink3Region) ) { + // 3 strong-linked Turbot fish found + ThreeStrongLinksHint hint = createHint(grid, digit, start1, bridge11, bridge12, + baseLink2Region, baseLink1Region, shareRegion1, end2, bridge21, bridge22, baseLink3Region, shareRegion2, baseLink2, baseLink1, baseLink3); + if (hint.isWorth()) + accu.add(hint); + } // if sharedRegion () && sharedRegion () && sharedRegion != bbaseLink regions + } // for int k = 0..2 + } // for int j = 0..2 + } // for int i = 0..2 + cells[4] = null; + cells[5] = null; + } //i3 + cells[2] = null; + cells[3] = null; + } //i2 + cells[0] = null; + cells[1] = null; + } //i1 + } //digit + } + + private ThreeStrongLinksHint createHint(Grid grid, int value, Cell start1, Cell bridgeCell11, Cell bridgeCell12, + Grid.Region baseLink1Set, Grid.Region baseLink2Set, Grid.Region shareRegion1, Cell end2, Cell bridgeCell21, Cell bridgeCell22, + Grid.Region baseLink3Set, Grid.Region shareRegion2, int baseLink1, int baseLink2, int baseLink3) { + // Build list of removable potentials + Map removablePotentials = new HashMap<>(); + CellSet victims = new CellSet(start1.getVisibleCells()); + victims.retainAll(end2.getVisibleCells()); + victims.remove(start1); + victims.remove(end2); + for (Cell cell : victims) { + if (grid.hasCellPotentialValue(cell.getIndex(), value)) + removablePotentials.put(cell, SingletonBitSet.create(value)); + } + + // Create hint + return new ThreeStrongLinksHint(this, removablePotentials, + start1, bridgeCell11, bridgeCell12, value, baseLink1Set, baseLink2Set, shareRegion1, bridgeCell21, bridgeCell22, end2, baseLink3Set, shareRegion2, baseLink1, baseLink2, baseLink3); + } + + @Override + public String toString() { + return "3-link Turbot Fishes"; + } +} diff --git a/diuf/sudoku/solver/rules/ThreeStrongLinksHint.html b/diuf/sudoku/solver/rules/ThreeStrongLinksHint.html new file mode 100644 index 0000000..1a2cb8b --- /dev/null +++ b/diuf/sudoku/solver/rules/ThreeStrongLinksHint.html @@ -0,0 +1,23 @@ + + +

    {0}

    +

    + This technique relies on Conjugate Pairs. + The value {1} in {8}, {9} and {10} forms + 3 strong links (conjugate pairs). The 1st set of bridge cells + {3} and {4} share the same region ({11}), + therefore forming a weak link. The 2nd set of bridge cells + {5} and {6} share the same region ({12}) which + means that they also form a weak link. The 3 strong links therfore + are joined with these weak links. +

    +

    + Any occurrence of the value {1} can therefore be + removed from any cells sharing a row, column or block + with both start cells {2} and {7}. +

    +

    + This technique is similar to a 2x2x2 Finned Sashimi Swordfish technique logic. +

    + + \ No newline at end of file diff --git a/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java b/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java new file mode 100644 index 0000000..3fc996f --- /dev/null +++ b/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java @@ -0,0 +1,395 @@ +package diuf.sudoku.solver.rules; + +import java.util.*; +import diuf.sudoku.*; +import diuf.sudoku.solver.*; +import diuf.sudoku.solver.rules.chaining.*; +import diuf.sudoku.tools.*; + + +/** + * Turbot Fish (with 3 Strong links) hints + */ +public class ThreeStrongLinksHint extends IndirectHint implements Rule, HasParentPotentialHint { + + private final int value; + private final int baseLinkType1; + private final int baseLinkType2; + private final int baseLinkType3; + private final Cell startCell; + private final Cell bridgeCell11; + private final Cell bridgeCell12; + private final Cell bridgeCell21; + private final Cell bridgeCell22; + private final Cell endCell; + private final Grid.Region baseLink1Set; + private final Grid.Region baseLink2Set; + private final Grid.Region baseLink3Set; + private final Grid.Region shareRegion1; + private final Grid.Region shareRegion2; + + public ThreeStrongLinksHint(IndirectHintProducer rule, Map removablePotentials, + Cell startCell, Cell bridgeCell11, Cell bridgeCell12, + int value, Grid.Region baseLink1Set, Grid.Region baseLink2Set, Grid.Region shareRegion1, + Cell bridgeCell21, Cell bridgeCell22, Cell endCell, Grid.Region baseLink3Set, Grid.Region shareRegion2, int baseLinkType1, int baseLinkType2, int baseLinkType3) { + super(rule, removablePotentials); + this.value = value; + this.baseLinkType1 = baseLinkType1; + this.baseLinkType2 = baseLinkType2; + this.baseLinkType3 = baseLinkType3; + this.startCell = startCell; + this.bridgeCell11 = bridgeCell11; + this.bridgeCell12 = bridgeCell12; + this.bridgeCell21 = bridgeCell21; + this.bridgeCell22 = bridgeCell22; + this.endCell = endCell; + this.baseLink1Set = baseLink1Set; + this.baseLink2Set = baseLink2Set; + this.baseLink3Set = baseLink3Set; + this.shareRegion1 = shareRegion1; + this.shareRegion2 = shareRegion2; + } + + @Override + public int getViewCount() { + return 1; + } + + @Override + public Cell[] getSelectedCells() { + return new Cell[] { startCell, endCell }; + } + + @Override + public Map getGreenPotentials(Grid grid, int viewNum) { + Map result = new HashMap<>(); + BitSet fishDigitSet = SingletonBitSet.create(value); + result.put(startCell, fishDigitSet); // orange + result.put(bridgeCell11, fishDigitSet); + result.put(bridgeCell12, fishDigitSet); // orange + result.put(bridgeCell21, fishDigitSet); + result.put(bridgeCell22, fishDigitSet); // orange + result.put(endCell, fishDigitSet); + return result; + } + + @Override + public Map getRedPotentials(Grid grid, int viewNum) { + Map result = new HashMap<>(super.getRemovablePotentials()); + BitSet fishDigitSet = SingletonBitSet.create(value); + result.put(startCell, fishDigitSet); + result.put(bridgeCell12, fishDigitSet); + result.put(bridgeCell22, fishDigitSet); + return result; + } + + @Override + public Collection getLinks(Grid grid, int viewNum) { + Collection result = new ArrayList<>(); + result.add(new Link(startCell, value, bridgeCell11, value)); + //result.add(new Link(bridgeCell11, value, bridgeCell12, value)); + result.add(new Link(bridgeCell12, value, bridgeCell21, value)); + //result.add(new Link(bridgeCell21, value, bridgeCell22, value)); + result.add(new Link(bridgeCell22, value, endCell, value)); + return result; + } + + @Override + public Grid.Region[] getRegions() { + //return new Grid.Region[] { shareRegion1 }; + return null; + } + + @Override + public String toString() { + return getName() + + ": " + + Cell.toFullString(startCell, bridgeCell11, bridgeCell12, bridgeCell21, bridgeCell22, endCell) + + " on value " + + value; + } + + @Override + public String toHtml(Grid grid) { + String result = HtmlLoader.loadHtml(this, "ThreeStrongLinksHint.html"); + String name = getName(); + String baseLink1Name = this.baseLink1Set.toFullString(); + String baseLink2Name = this.baseLink2Set.toFullString(); + String baseLink3Name = this.baseLink3Set.toFullString(); + String shared1 = this.shareRegion1.toFullString(); + String shared2 = this.shareRegion2.toFullString(); + String value = Integer.toString(this.value); + String cell1 = startCell.toString(); + String cell2 = bridgeCell11.toString(); + String cell3 = bridgeCell12.toString(); + String cell4 = bridgeCell21.toString(); + String cell5 = bridgeCell22.toString(); + String cell6 = endCell.toString(); + result = HtmlLoader.format(result, name, value, cell1, cell2, cell3, cell4, cell5,cell6, baseLink1Name, baseLink2Name, baseLink3Name, shared1, shared2); + return result; + } + + private String getSuffix() { + String nameSuffix; + if (baseLinkType1 > 0) { + nameSuffix = "1"; + } + else { + nameSuffix = "0"; + } + if ((baseLinkType2 > 0) && (baseLinkType1 == baseLinkType2 || nameSuffix == "0")) { + nameSuffix +="1"; + } + else { + if (baseLinkType2 > 0) { + nameSuffix +="2"; + } + else { + nameSuffix +="0"; + } + } + if ((baseLinkType3 > 0) && (nameSuffix.contains("00") || baseLinkType1 == baseLinkType3 || (baseLinkType2 != baseLinkType3 && nameSuffix.indexOf("2") == 1) || (baseLinkType2 == baseLinkType3 && nameSuffix.indexOf("1") == 1))) { + nameSuffix +="1"; + } + else { + if (baseLinkType3 > 0) { + nameSuffix +="2"; + } + else { + nameSuffix +="0"; + } + } + if (nameSuffix.contains("122")) + return "112"; + if (nameSuffix.contains("120")) + return "012"; + return nameSuffix; + } + + + @Override + public String getName() { + Class region1 = baseLink1Set.getClass(); + Class region2 = baseLink2Set.getClass(); + Class region3 = baseLink3Set.getClass(); + String suffix = getSuffix(); + if (region1 == Grid.Row.class) { + if (region2 == Grid.Row.class) { + if (region3 == Grid.Row.class) { + return "Skyscraper (with 3 Strong links)" + " " + suffix; + } + else { + if (region3 == Grid.Column.class) { + return "Three-String Kite" + " " + suffix; + } + else { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + } + else { + if (region2 == Grid.Column.class) { + return "Three-String Kite" + " " + suffix; + } + else { + if (region3 == Grid.Column.class) { + return "Three-String Kite" + " " + suffix; + } + else { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + } + } + if (region1 == Grid.Column.class) { + if (region2 == Grid.Column.class) { + if (region3 == Grid.Column.class) { + return "Skyscraper (with 3 Strong links)" + " " + suffix; + } + else { + if (region3 == Grid.Row.class) { + return "Three-String Kite" + " " + suffix; + } + else { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + } + else { + if (region2 == Grid.Row.class) { + return "Three-String Kite" + " " + suffix; + } + else { + if (region3 == Grid.Row.class) { + return "Three-String Kite" + " " + suffix; + } + else { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + } + } + if (region1 == Grid.Block.class) { + if (region2 == Grid.Row.class) { + if (region3 == Grid.Column.class) { + return "Three-String Kite" + " " + suffix; + } + else { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + if (region2 == Grid.Column.class) { + if (region3 == Grid.Row.class) { + return "Three-String Kite" + " " + suffix; + } + else { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + if (region2 == Grid.Block.class) { + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + } + return "Turbot Fish (with 3 Strong links)" + " " + suffix; + } + + @Override + public String getShortName() { + Class region1 = baseLink1Set.getClass(); + Class region2 = baseLink2Set.getClass(); + Class region3 = baseLink3Set.getClass(); + String suffix = getSuffix(); + if (region1 == Grid.Row.class) { + if (region2 == Grid.Row.class) { + if (region3 == Grid.Row.class) { + return "3Sk" + " " + suffix; + } + else { + if (region3 == Grid.Column.class) { + return "3SK" + " " + suffix; + } + else { + return "3TF" + " " + suffix; + } + } + } + else { + if (region2 == Grid.Column.class) { + return "3SK" + " " + suffix; + } + else { + if (region3 == Grid.Column.class) { + return "3SK" + " " + suffix; + } + else { + return "3TF" + " " + suffix; + } + } + } + } + if (region1 == Grid.Column.class) { + if (region2 == Grid.Column.class) { + if (region3 == Grid.Column.class) { + return "3Sk" + " " + suffix; + } + else { + if (region3 == Grid.Row.class) { + return "3SK" + " " + suffix; + } + else { + return "3TF" + " " + suffix; + } + } + } + else { + if (region2 == Grid.Row.class) { + return "3SK" + " " + suffix; + } + else { + if (region3 == Grid.Row.class) { + return "3SK" + " " + suffix; + } + else { + return "3TF" + " " + suffix; + } + } + } + } + if (region1 == Grid.Block.class) { + if (region2 == Grid.Row.class) { + if (region3 == Grid.Column.class) { + return "3SK" + " " + suffix; + } + else { + return "3TF" + " " + suffix; + } + } + if (region2 == Grid.Column.class) { + if (region3 == Grid.Row.class) { + return "3SK" + " " + suffix; + } + else { + return "3TF" + " " + suffix; + } + } + if (region2 == Grid.Block.class) { + return "3TF" + " " + suffix; + } + } + return "3TF" + " " + suffix; + } + + + @Override + public double getDifficulty() { + String name = getName(); + if (name.contains("Skyscraper")) { + return 5.4; + } else if (name.contains("Three-String Kite")) { + return 5.6; + } else { + return 5.5; + } + } + + @Override + public int hashCode() { + return startCell.hashCode() ^ endCell.hashCode() ^ + bridgeCell11.hashCode() ^ bridgeCell12.hashCode() ^ value; + } + + + public String getClueHtml(Grid grid, boolean isBig) { + if (isBig) { + return "Look for a " + getName() + " on the value " + value; + } else { + return "Look for a " + getName(); + } + } + + + + @Override + public Collection getRuleParents(Grid initialGrid, Grid currentGrid) { + Collection result = new ArrayList<>(); + Cell startCell = Grid.getCell(this.startCell.getIndex()); + Cell endCell = Grid.getCell(this.endCell.getIndex()); + Cell bridgeCell11 = Grid.getCell(this.bridgeCell11.getIndex()); + Cell bridgeCell12 = Grid.getCell(this.bridgeCell12.getIndex()); + Cell bridgeCell21 = Grid.getCell(this.bridgeCell21.getIndex()); + Cell bridgeCell22 = Grid.getCell(this.bridgeCell22.getIndex()); + if (initialGrid.hasCellPotentialValue(startCell.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.startCell.getIndex(), value)) + result.add(new Potential(this.startCell, value, false)); + if (initialGrid.hasCellPotentialValue(bridgeCell11.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.bridgeCell11.getIndex(), value)) + result.add(new Potential(this.bridgeCell11, value, false)); + if (initialGrid.hasCellPotentialValue(bridgeCell12.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.bridgeCell12.getIndex(), value)) + result.add(new Potential(this.bridgeCell12, value, false)); + if (initialGrid.hasCellPotentialValue(bridgeCell21.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.bridgeCell21.getIndex(), value)) + result.add(new Potential(this.bridgeCell21, value, false)); + if (initialGrid.hasCellPotentialValue(bridgeCell22.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.bridgeCell22.getIndex(), value)) + result.add(new Potential(this.bridgeCell22, value, false)); + if (initialGrid.hasCellPotentialValue(endCell.getIndex(), value) && !initialGrid.hasCellPotentialValue(this.endCell.getIndex(), value)) + result.add(new Potential(this.endCell, value, false)); + return result; + } +} diff --git a/diuf/sudoku/solver/rules/TurbotFishHint.html b/diuf/sudoku/solver/rules/TurbotFishHint.html index 0330759..27e6db8 100644 --- a/diuf/sudoku/solver/rules/TurbotFishHint.html +++ b/diuf/sudoku/solver/rules/TurbotFishHint.html @@ -11,10 +11,10 @@

    {0}

    Any occurrence of the value {1} can therefore be removed from any cells sharing a row, column or block - with all with both start cells {2} and {5}. + with both start cells {2} and {5}.

    - This technique is similar to {9} technique logic. + This technique is similar to a 2x2 Finned Sashimi X-Wing technique logic.

    \ No newline at end of file diff --git a/diuf/sudoku/solver/rules/TurbotFishHint.java b/diuf/sudoku/solver/rules/TurbotFishHint.java index c1820d7..2efcd2e 100644 --- a/diuf/sudoku/solver/rules/TurbotFishHint.java +++ b/diuf/sudoku/solver/rules/TurbotFishHint.java @@ -101,13 +101,7 @@ public String toHtml(Grid grid) { String cell2 = bridgeCell1.toString(); String cell3 = bridgeCell2.toString(); String cell4 = endCell.toString(); - String finned = "finned mutant sashimi x-wing"; - if (name == "Skyscraper") - finned = "finned sashimi x-wing"; - else - if (name == "Turbot Fish") - finned = "finned franken sashimi x-wing"; - result = HtmlLoader.format(result, name, value, cell1, cell2, cell3, cell4, base, cover, shared, finned); + result = HtmlLoader.format(result, name, value, cell1, cell2, cell3, cell4, base, cover, shared); return result; } From e09bb8a4763c49d13026f90ef6e0bd07306e7fb1 Mon Sep 17 00:00:00 2001 From: SudokuMonster Date: Tue, 15 Oct 2019 22:52:26 +0100 Subject: [PATCH 3/3] Minor Tidying up --- diuf/sudoku/Settings.java | 4 +- diuf/sudoku/gui/GenerateDialog.java | 2 +- diuf/sudoku/gui/ThreeStrongLinks.html | 4 +- diuf/sudoku/solver/Solver.java | 2 - .../sudoku/solver/rules/ThreeStrongLinks.java | 15 +++++-- .../solver/rules/ThreeStrongLinksHint.java | 40 +++++++++---------- 6 files changed, 36 insertions(+), 31 deletions(-) diff --git a/diuf/sudoku/Settings.java b/diuf/sudoku/Settings.java index e24bc81..839bcb6 100644 --- a/diuf/sudoku/Settings.java +++ b/diuf/sudoku/Settings.java @@ -18,8 +18,8 @@ public class Settings { public final static int VERSION = 1; public final static int REVISION = 6; - public final static String SUBREV = ".0"; - public final static String releaseDate = "2019-10-14"; + public final static String SUBREV = ".1"; + public final static String releaseDate = "2019-10-15"; public final static String releaseYear = "2019"; public final static String releaseLicence = "Lesser General Public License"; public final static String releaseLicenceMini = "LGPL"; diff --git a/diuf/sudoku/gui/GenerateDialog.java b/diuf/sudoku/gui/GenerateDialog.java index e710dce..17e78d4 100644 --- a/diuf/sudoku/gui/GenerateDialog.java +++ b/diuf/sudoku/gui/GenerateDialog.java @@ -1085,7 +1085,7 @@ public String getexcludeTechnique3() { return ""; } public String getincludeTechnique1() { - return ""; + return "ligned"; } @Override public String getincludeTechnique2() { diff --git a/diuf/sudoku/gui/ThreeStrongLinks.html b/diuf/sudoku/gui/ThreeStrongLinks.html index edf0f11..c0724dc 100644 --- a/diuf/sudoku/gui/ThreeStrongLinks.html +++ b/diuf/sudoku/gui/ThreeStrongLinks.html @@ -1,6 +1,6 @@ - 3 strong links: Requires a 3 Strong-link fish to solve in 102 formation - Rating: 5.6 + 3 strong links: Requires a fish with 3 strong links (101 or 102 formation) + Rating: 5.5-5.6 \ No newline at end of file diff --git a/diuf/sudoku/solver/Solver.java b/diuf/sudoku/solver/Solver.java index 9b565f4..199a7df 100644 --- a/diuf/sudoku/solver/Solver.java +++ b/diuf/sudoku/solver/Solver.java @@ -158,8 +158,6 @@ public Solver(Grid grid) { addIfWorth(SolvingTechnique.HiddenQuad, indirectHintProducers, new HiddenSet(4, false)); addIfWorth(SolvingTechnique.Jellyfish, indirectHintProducers, new Fisherman(4)); addIfWorth(SolvingTechnique.WXYZWing, indirectHintProducers, new WXYZWing()); - //addIfWorth(SolvingTechnique.VWXYZWing4, indirectHintProducers, new VWXYZWing(true)); - //addIfWorth(SolvingTechnique.VWXYZWing5, indirectHintProducers, new VWXYZWing(false)); addIfWorth(SolvingTechnique.BivalueUniversalGrave, indirectHintProducers, new BivalueUniversalGrave()); addIfWorth(SolvingTechnique.VWXYZWing, indirectHintProducers, new VWXYZWing()); addIfWorth(SolvingTechnique.AlignedPairExclusion, indirectHintProducers, new AlignedPairExclusion()); diff --git a/diuf/sudoku/solver/rules/ThreeStrongLinks.java b/diuf/sudoku/solver/rules/ThreeStrongLinks.java index b287411..8e254a1 100644 --- a/diuf/sudoku/solver/rules/ThreeStrongLinks.java +++ b/diuf/sudoku/solver/rules/ThreeStrongLinks.java @@ -41,10 +41,17 @@ private Grid.Region shareRegionOf(Grid grid, } else return null; } - private boolean isSamebox (Cell boxCell1, Cell boxCell2) { +/* private boolean isSameBox (Cell boxCell1, Cell boxCell2) { if (boxCell1.getB() == boxCell2.getB()) return true; return false; + }*/ + + + private boolean isSameLine (Cell lineCell1, Cell lineCell2) { + if (lineCell1.getX() == lineCell2.getX() || lineCell1.getY() == lineCell2.getY()) + return true; + return false; } private void getHints(Grid grid, HintsAccumulator accu, @@ -64,7 +71,7 @@ private void getHints(Grid grid, HintsAccumulator accu, // region 1 Cell regionCell1 = baseLink1Region.getCell(p1 = baseLink1RegionPotentials.nextSetBit(0)); Cell regionCell2 = baseLink1Region.getCell(baseLink1RegionPotentials.nextSetBit(p1 + 1)); - if (baseLink1 > 0 && isSamebox(regionCell1,regionCell2)) + if (baseLink1 == 0 && isSameLine(regionCell1,regionCell2)) continue; cells[0] = regionCell1; cells[1] = regionCell2; @@ -76,7 +83,7 @@ private void getHints(Grid grid, HintsAccumulator accu, // region 2 regionCell1 = baseLink2Region.getCell(p2 = baseLink2RegionPotentials.nextSetBit(0)); regionCell2 = baseLink2Region.getCell(baseLink2RegionPotentials.nextSetBit(p2 + 1)); - if (baseLink2 > 0 && isSamebox(regionCell1,regionCell2)) + if (baseLink2 == 0 && isSameLine(regionCell1,regionCell2)) continue; cells[2] = regionCell1; cells[3] = regionCell2; @@ -87,7 +94,7 @@ private void getHints(Grid grid, HintsAccumulator accu, continue; regionCell1 = baseLink3Region.getCell(p3 = baseLink3RegionPotentials.nextSetBit(0)); regionCell2 = baseLink3Region.getCell(baseLink3RegionPotentials.nextSetBit(p3 + 1)); - if (baseLink3 > 0 && isSamebox(regionCell1,regionCell2)) + if (baseLink3 == 0 && isSameLine(regionCell1,regionCell2)) continue; cells[4] = regionCell1; cells[5] = regionCell2; diff --git a/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java b/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java index 3fc996f..71b0761 100644 --- a/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java +++ b/diuf/sudoku/solver/rules/ThreeStrongLinksHint.java @@ -8,7 +8,7 @@ /** - * Turbot Fish (with 3 Strong links) hints + * 3-Turbot Fish hints */ public class ThreeStrongLinksHint extends IndirectHint implements Rule, HasParentPotentialHint { @@ -176,27 +176,27 @@ public String getName() { if (region1 == Grid.Row.class) { if (region2 == Grid.Row.class) { if (region3 == Grid.Row.class) { - return "Skyscraper (with 3 Strong links)" + " " + suffix; + return "3 Skyscrapers" + " " + suffix; } else { if (region3 == Grid.Column.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } } else { if (region2 == Grid.Column.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { if (region3 == Grid.Column.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } } @@ -204,27 +204,27 @@ public String getName() { if (region1 == Grid.Column.class) { if (region2 == Grid.Column.class) { if (region3 == Grid.Column.class) { - return "Skyscraper (with 3 Strong links)" + " " + suffix; + return "3 Skyscrapers" + " " + suffix; } else { if (region3 == Grid.Row.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } } else { if (region2 == Grid.Row.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { if (region3 == Grid.Row.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } } @@ -232,25 +232,25 @@ public String getName() { if (region1 == Grid.Block.class) { if (region2 == Grid.Row.class) { if (region3 == Grid.Column.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } if (region2 == Grid.Column.class) { if (region3 == Grid.Row.class) { - return "Three-String Kite" + " " + suffix; + return "3-String Kite" + " " + suffix; } else { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } if (region2 == Grid.Block.class) { - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } } - return "Turbot Fish (with 3 Strong links)" + " " + suffix; + return "3-Turbot Fish" + " " + suffix; } @Override @@ -345,7 +345,7 @@ public double getDifficulty() { String name = getName(); if (name.contains("Skyscraper")) { return 5.4; - } else if (name.contains("Three-String Kite")) { + } else if (name.contains("3-String Kite")) { return 5.6; } else { return 5.5;