From 6ed84e5ea42b97d6b942735b83e7e2ae9ee48915 Mon Sep 17 00:00:00 2001 From: p9malino26 <> Date: Mon, 31 Oct 2022 12:47:55 +0000 Subject: [PATCH] Add solver and random game generator --- src/com/patryk/mathdoku/ArrayConversions.java | 42 ++++++++ src/com/patryk/mathdoku/MathDoku.java | 47 +++++++-- src/com/patryk/mathdoku/UserData.java | 39 ++++++-- src/com/patryk/mathdoku/cageData/Cage.java | 23 ++++- .../patryk/mathdoku/cageData/CageData.java | 36 ++++--- src/com/patryk/mathdoku/cageData/Cell.java | 17 ++++ .../mathdoku/errorChecking/CageInfo.java | 2 +- ...rsiveSolver.java => RecursiveChecker.java} | 13 ++- src/com/patryk/mathdoku/gui/GameGridView.java | 4 +- src/com/patryk/mathdoku/gui/GameUI.java | 6 +- .../mathdoku/gui/drawers/CageDrawer.java | 2 +- .../patryk/mathdoku/randomGame/FloodFunc.java | 54 +++++++++++ .../mathdoku/randomGame/RandomCalcTree.java | 35 +++++++ .../mathdoku/randomGame/RandomGame.java | 63 ++++++++++++ .../randomGame/RandomPartitionGrid.java | 47 +++++++++ .../mathdoku/randomGame/RandomUserData.java | 56 +++++++++++ .../patryk/mathdoku/solver/CageSolver.java | 95 +++++++++++++++++++ src/com/patryk/mathdoku/solver/Solver.java | 75 +++++++++++++++ src/com/patryk/mathdoku/solver/pseudocode.txt | 42 ++++++++ src/com/patryk/mathdoku/util/BoardPosVec.java | 46 +++++++++ 20 files changed, 706 insertions(+), 38 deletions(-) create mode 100644 src/com/patryk/mathdoku/ArrayConversions.java create mode 100644 src/com/patryk/mathdoku/cageData/Cell.java rename src/com/patryk/mathdoku/errorChecking/{RecursiveSolver.java => RecursiveChecker.java} (76%) create mode 100644 src/com/patryk/mathdoku/randomGame/FloodFunc.java create mode 100644 src/com/patryk/mathdoku/randomGame/RandomCalcTree.java create mode 100644 src/com/patryk/mathdoku/randomGame/RandomGame.java create mode 100644 src/com/patryk/mathdoku/randomGame/RandomPartitionGrid.java create mode 100644 src/com/patryk/mathdoku/randomGame/RandomUserData.java create mode 100644 src/com/patryk/mathdoku/solver/CageSolver.java create mode 100644 src/com/patryk/mathdoku/solver/Solver.java create mode 100644 src/com/patryk/mathdoku/solver/pseudocode.txt diff --git a/src/com/patryk/mathdoku/ArrayConversions.java b/src/com/patryk/mathdoku/ArrayConversions.java new file mode 100644 index 0000000..fb7c3c1 --- /dev/null +++ b/src/com/patryk/mathdoku/ArrayConversions.java @@ -0,0 +1,42 @@ +package com.patryk.mathdoku; + +public class ArrayConversions { + public static float[] toNative(Float[] arr) { + float[] out = new float[arr.length]; + for(int i = 0; i < arr.length; ++i) { + out[i] = arr[i]; + } + + return out; + } + + public static int[] toNative(Object[] arr) { + + int[] out = new int[arr.length]; + for(int i = 0; i < arr.length; ++i) { + out[i] = (Integer)arr[i]; + } + + return out; + } + + public static double[] toDouble(float[] arr) { + double[] out = new double[arr.length]; + for(int i = 0; i < arr.length; ++i) { + out[i] = arr[i]; + } + + return out; + } + + public static Float[] fromNative(float[] arr) { + Float[] out = new Float[arr.length]; + for(int i = 0; i < arr.length; ++i) { + out[i] = arr[i]; + } + + return out; + } + +} + diff --git a/src/com/patryk/mathdoku/MathDoku.java b/src/com/patryk/mathdoku/MathDoku.java index d6c0d7a..3910f26 100755 --- a/src/com/patryk/mathdoku/MathDoku.java +++ b/src/com/patryk/mathdoku/MathDoku.java @@ -1,8 +1,14 @@ package com.patryk.mathdoku; +import com.patryk.mathdoku.cageData.Cage; +import com.patryk.mathdoku.cageData.CageData; import com.patryk.mathdoku.cageData.DataFormatException; +import com.patryk.mathdoku.solver.CageSolver; import com.patryk.mathdoku.gui.GameUI; import com.patryk.mathdoku.gui.ManualGameInputDialog; +import com.patryk.mathdoku.gui.RandGameDialog; +import com.patryk.mathdoku.randomGame.RandomGame; +import com.patryk.mathdoku.solver.Solver; import javafx.application.Application; import javafx.application.Platform; import javafx.event.ActionEvent; @@ -16,7 +22,9 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.List; import java.util.Optional; +import java.util.Random; public class MathDoku extends Application { GameUI gameUI; @@ -65,6 +73,29 @@ public class MathDoku extends Application { }; + EventHandler<ActionEvent> onRanGameButtonPressed = (event) -> { + RandGameDialog dialog = new RandGameDialog(); + + Optional<RandGameDialog.RanGameInfo> result = dialog.showAndWait(); + + if (result.isPresent()) { + int seed; + seed = result.get().seed; + int size = result.get().width; + + CageData cageData = RandomGame.randomGame(seed, size); + setGameContext(new GameContext(cageData)); + } + + + }; + + private void onSolveButtonPressed(ActionEvent event) { + gameContext.userData.clear(); + gameUI.wonBefore = true; + Solver.solve(gameContext.getUserData(), gameContext.getCageData()); + } + //key event handler @@ -124,6 +155,8 @@ public class MathDoku extends Application { gameUI.checkButton.addEventHandler(ActionEvent.ANY,(event) -> gameUI.gameGridView.showErrors()); gameUI.fileLoadButton.addEventHandler(ActionEvent.ANY, onFileLoadButtonPressed); gameUI.textLoadButton.addEventHandler(ActionEvent.ANY, onManualInputButtonPressed); + gameUI.ranGameButton.addEventHandler(ActionEvent.ANY, onRanGameButtonPressed); + gameUI.solveButton.addEventHandler(ActionEvent.ANY, this::onSolveButtonPressed); gameUI.setNumberButtonCallback((digit) -> gameUI.gameGridView.getInputHandler().injectNumberKey(digit)); @@ -136,13 +169,15 @@ public class MathDoku extends Application { } public boolean wantsToExit() { - if (shouldDisplayExitDialog()) { - //show confirmation dialog - - - return gameUI.showConfirmExitDialog(); - } + //todo debug return true; +// if (shouldDisplayExitDialog()) { +// //show confirmation dialog +// +// +// return gameUI.showConfirmExitDialog(); +// } +// return true; } public boolean shouldDisplayExitDialog() { diff --git a/src/com/patryk/mathdoku/UserData.java b/src/com/patryk/mathdoku/UserData.java index 608db49..55363ff 100755 --- a/src/com/patryk/mathdoku/UserData.java +++ b/src/com/patryk/mathdoku/UserData.java @@ -5,9 +5,7 @@ import com.patryk.mathdoku.util.BoardPosVec; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; +import java.util.*; public class UserData { public interface ChangeListener { @@ -98,6 +96,10 @@ public class UserData { notifyListener(new ChangeListener.MultipleCellChange(false)); } + public int getBoardWidth() { + return boardWidth; + } + public int getPopulationCount() { return populationCount; } @@ -122,17 +124,20 @@ public class UserData { public void setValueAtCell(BoardPosVec cell, int value) { - int oldValue = data[cell.toIndex()]; + setValueAtCell(cell.toIndex(), value); + } + public void setValueAtCell(int index, int value) { + int oldValue = data[index]; if (oldValue != value) { - data[cell.toIndex()] = value; + data[index] = value; if (oldValue == 0) populationCount++; if (value == 0) { populationCount--; } - notifyListener(new ChangeListener.SingleCellChange(cell, oldValue, value)); + notifyListener(new ChangeListener.SingleCellChange(new BoardPosVec(index), oldValue, value)); } } @@ -167,6 +172,28 @@ public class UserData { return builder.toString(); } + public Set<Integer> getAllowedDigits(int index) { + BoardPosVec posVec = new BoardPosVec(index); + int r = posVec.r; + int c = posVec.c; + + Set<Integer> allDigits = new HashSet<>(); + for(int i = 1; i <= boardWidth; i++) { + allDigits.add(i); + } + + + //remove row digits + for (int vcol = 0; vcol < c; vcol++) { + allDigits.remove(Integer.valueOf(getValueAtCell(new BoardPosVec(r, vcol)))); + } + //remove column digits + for (int vrow = 0; vrow < r; vrow++) { + allDigits.remove(Integer.valueOf(getValueAtCell(new BoardPosVec(vrow, c)))); + } + return allDigits; + } + public void fill() { for (int i = 0; i < fullSize;i ++) { int val = (int)( 5 * Math.random()) + 1; diff --git a/src/com/patryk/mathdoku/cageData/Cage.java b/src/com/patryk/mathdoku/cageData/Cage.java index 147398d..f865fc8 100755 --- a/src/com/patryk/mathdoku/cageData/Cage.java +++ b/src/com/patryk/mathdoku/cageData/Cage.java @@ -1,12 +1,15 @@ package com.patryk.mathdoku.cageData; +import java.util.ArrayList; import java.util.List; public class Cage { int target; - Cage.Operator operator; - int markedCell; - private final List<Integer> memberCells; + Cage.Operator operator = Operator.ADD; + int markedCell = 0; + private List<Integer> memberCells = new ArrayList<>(); + + public Cage() {} public Cage(int target, Cage.Operator operator, int markedCell, List<Integer> memberCells) { this.target = target; @@ -39,6 +42,18 @@ public class Cage { return s; } + public void setTarget(int target) { + this.target = target; + } + + public void setOperator(Operator operator) { + this.operator = operator; + } + + public void setMemberCells(List<Integer> memberCells) { + this.memberCells = memberCells; + } + public enum Operator { ADD('+'), SUBTRACT('-'), MULTIPLY('x'), DIVIDE('รท'); @@ -72,7 +87,7 @@ public class Cage { case MULTIPLY: return operand1 * operand2; case DIVIDE: - return Math.floorDiv(operand1, operand2); + return Math.max(1, Math.floorDiv(operand1, operand2)); default: return -1; } diff --git a/src/com/patryk/mathdoku/cageData/CageData.java b/src/com/patryk/mathdoku/cageData/CageData.java index 58c8115..9eba412 100755 --- a/src/com/patryk/mathdoku/cageData/CageData.java +++ b/src/com/patryk/mathdoku/cageData/CageData.java @@ -8,18 +8,34 @@ import java.util.*; public class CageData { int width; - Cell[] data; + + public List<Cage> getCageList() { + return cageList; + } + List<Cage> cageList = new ArrayList<Cage>(); int cageCount; + public CageData(int width) { + this.width = width; + data = new Cell[width * width]; + for (int i = 0; i < width * width; i++) { + data[i] = new Cell(-1); + } + this.cageList = new ArrayList<>(); + + } public CageData(String data) throws DataFormatException{ parseData(data); } - + public Cell[] getData() { + return data; + } public List<Cage> getCages() {return cageList; } + public boolean cellConnectsTo(BoardPosVec pos, Direction direction) { Cell nextCell = getCellAt(pos.add(direction.vector)); @@ -45,7 +61,7 @@ public class CageData { if (size > 81) throw new DataFormatException(-1, "Too many cells! Max is 81."); initData(); - cageCount = parser.parseData(new Scanner(rawData), data, cageList); + parser.parseData(new Scanner(rawData), data, cageList); } @@ -71,17 +87,11 @@ public class CageData { } public int getCageCount() { - return cageCount; + return cageList.size(); } -} -class Cell { - private int cageID; - - public Cell (int cageID) { - this.cageID = cageID; + public void setCageList(List<Cage> cageList) { + this.cageList = cageList; } - - public int getCageId() {return cageID; } -} \ No newline at end of file +} diff --git a/src/com/patryk/mathdoku/cageData/Cell.java b/src/com/patryk/mathdoku/cageData/Cell.java new file mode 100644 index 0000000..ee915fc --- /dev/null +++ b/src/com/patryk/mathdoku/cageData/Cell.java @@ -0,0 +1,17 @@ +package com.patryk.mathdoku.cageData; + +public class Cell { + private int cageID; + + public Cell(int cageID) { + this.cageID = cageID; + } + + public int getCageId() { + return cageID; + } + + public void setCageId(int cageID) { + this.cageID = cageID; + } +} diff --git a/src/com/patryk/mathdoku/errorChecking/CageInfo.java b/src/com/patryk/mathdoku/errorChecking/CageInfo.java index 6e9dab6..82db135 100755 --- a/src/com/patryk/mathdoku/errorChecking/CageInfo.java +++ b/src/com/patryk/mathdoku/errorChecking/CageInfo.java @@ -45,7 +45,7 @@ public class CageInfo { populationCount++; if (isFull()) { int[] cageMemberData = getMembersOfCage(); - isInvalid = !RecursiveSolver.testSign(cageMemberData, cage.getTarget(), cage.getOperator()); + isInvalid = !RecursiveChecker.testSign(cageMemberData, cage.getTarget(), cage.getOperator()); } } diff --git a/src/com/patryk/mathdoku/errorChecking/RecursiveSolver.java b/src/com/patryk/mathdoku/errorChecking/RecursiveChecker.java similarity index 76% rename from src/com/patryk/mathdoku/errorChecking/RecursiveSolver.java rename to src/com/patryk/mathdoku/errorChecking/RecursiveChecker.java index 478fa93..d9150ab 100755 --- a/src/com/patryk/mathdoku/errorChecking/RecursiveSolver.java +++ b/src/com/patryk/mathdoku/errorChecking/RecursiveChecker.java @@ -1,8 +1,11 @@ package com.patryk.mathdoku.errorChecking; +import com.patryk.mathdoku.ArrayConversions; import com.patryk.mathdoku.cageData.Cage; -public class RecursiveSolver { +import java.util.List; + +public class RecursiveChecker { @@ -35,14 +38,18 @@ public class RecursiveSolver { return false; } - private RecursiveSolver(int target, Cage.Operator operator) { + private RecursiveChecker(int target, Cage.Operator operator) { this.target = target; this.operator = operator; this.permute = (operator == Cage.Operator.SUBTRACT || operator == Cage.Operator.DIVIDE); } + + public static boolean testSign(List<Integer> cageList, int target, Cage.Operator operator) { + return testSign(ArrayConversions.toNative(cageList.toArray()), target, operator); + } public static boolean testSign(int[] cageList, int target, Cage.Operator operator) { - return new RecursiveSolver(target, operator).f(0, cageList, 0, 0); + return new RecursiveChecker(target, operator).f(0, cageList, 0, 0); } } \ No newline at end of file diff --git a/src/com/patryk/mathdoku/gui/GameGridView.java b/src/com/patryk/mathdoku/gui/GameGridView.java index 4caa5ab..e845680 100755 --- a/src/com/patryk/mathdoku/gui/GameGridView.java +++ b/src/com/patryk/mathdoku/gui/GameGridView.java @@ -44,7 +44,7 @@ public class GameGridView { } }; - UserData.ChangeListener redrawGame = (data) -> { + public void redrawGame() { userDataDrawer.draw(); errorHighlighter.clearCanvas(); }; @@ -88,7 +88,7 @@ public class GameGridView { this.gameContext = gameContext; //Drawer.init(gameContext.getBoardWidth(), pixelWidth); - gameContext.getUserData().addChangeListener(redrawGame); + gameContext.getUserData().addChangeListener(data -> redrawGame()); //link cage drawer to canvas and draw the cages diff --git a/src/com/patryk/mathdoku/gui/GameUI.java b/src/com/patryk/mathdoku/gui/GameUI.java index 4ca2379..4ea4f01 100755 --- a/src/com/patryk/mathdoku/gui/GameUI.java +++ b/src/com/patryk/mathdoku/gui/GameUI.java @@ -26,7 +26,7 @@ public class GameUI { private static final int MAX_WIDTH = 8; private Scene scene; GameContext gameContext; - private boolean wonBefore = false; + public boolean wonBefore = false; public boolean hasWonBefore() { return wonBefore; @@ -38,8 +38,10 @@ public class GameUI { public Button redoButton = new Button("Redo"); public Button clearButton = new Button("Clear"); public Button checkButton = new Button("Check"); + public Button solveButton = new Button("Solve"); public Button fileLoadButton = new Button("Load game from file"); public Button textLoadButton = new Button("Load game from text input"); + public Button ranGameButton = new Button("Random game"); public ComboBox<GameGridView.FontSize> fontSizeComboBox; GameGridView.FontSize defaultFontSize = GameGridView.FontSize.MEDIUM; @@ -81,7 +83,7 @@ public class GameUI { undoRedoButtonManager = new UndoRedoButtonManager(undoButton, redoButton); //initialize control pane controlPane = new VBox(); - controlPane.getChildren().addAll(undoButton, redoButton, clearButton, checkButton, fileLoadButton, textLoadButton); + controlPane.getChildren().addAll(undoButton, redoButton, clearButton, checkButton, solveButton, fileLoadButton, textLoadButton, ranGameButton); //disable clear and check buttons clearButton.setDisable(true); diff --git a/src/com/patryk/mathdoku/gui/drawers/CageDrawer.java b/src/com/patryk/mathdoku/gui/drawers/CageDrawer.java index 2d99d6d..9a50156 100755 --- a/src/com/patryk/mathdoku/gui/drawers/CageDrawer.java +++ b/src/com/patryk/mathdoku/gui/drawers/CageDrawer.java @@ -90,7 +90,7 @@ public class CageDrawer extends Drawer{ for (Cage c: data.getCages()) { - drawCageText(new BoardPosVec(c.getMarkedCell()), c.toString()); + drawCageText(new BoardPosVec(c.getMemberCells().get(0)), c.toString()); } } diff --git a/src/com/patryk/mathdoku/randomGame/FloodFunc.java b/src/com/patryk/mathdoku/randomGame/FloodFunc.java new file mode 100644 index 0000000..f30adfe --- /dev/null +++ b/src/com/patryk/mathdoku/randomGame/FloodFunc.java @@ -0,0 +1,54 @@ +package com.patryk.mathdoku.randomGame; + +import com.patryk.mathdoku.cageData.Cell; +import com.patryk.mathdoku.util.BoardPosVec; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +class FloodFunc { + Cell[] grid; + int maxCount; + int id; + Random ranObj; + + List<Integer> members = new ArrayList(); + + public FloodFunc(Cell[] grid, int maxCount, int id, Random ranObj) { + this.grid = grid; + this.maxCount = maxCount; + this.id = id; + this.ranObj = ranObj; + } + + public static List<Integer> doit(Cell[] grid, BoardPosVec pos, int maxCount, int id, Random ranObj) { + var obj = new FloodFunc(grid, maxCount, id, ranObj); + obj.rec(pos, pos); + return obj.members; + } + + public void rec(BoardPosVec pos, BoardPosVec oldPos) { + //if on cell with other id, return + //place mark + //decrement count + //while you can still colour more fields, for every block around you in random order that you did not come from, recurse + + if (grid[pos.toIndex()].getCageId() != -1) { + return; + } + + grid[pos.toIndex()].setCageId(id); + members.add(pos.toIndex()); + + maxCount--; + + BoardPosVec.forEveryCellAround(pos, ranObj, varPos -> { + if (maxCount > 0) { + if (varPos.equals(oldPos)) return; + rec(varPos, pos); + } + }); + + } +} diff --git a/src/com/patryk/mathdoku/randomGame/RandomCalcTree.java b/src/com/patryk/mathdoku/randomGame/RandomCalcTree.java new file mode 100644 index 0000000..a9f19ea --- /dev/null +++ b/src/com/patryk/mathdoku/randomGame/RandomCalcTree.java @@ -0,0 +1,35 @@ +package com.patryk.mathdoku.randomGame; + +import com.patryk.mathdoku.cageData.Cage; + +import java.util.List; +import java.util.Random; + +class RandomCalcTree { + List<Integer> values; + Random ranObj; + Cage.Operator operator; + + public RandomCalcTree(List<Integer> values, Random ranObj, Cage.Operator operator) { + this.values = values; + this.ranObj = ranObj; + this.operator = operator; + assert !values.contains(0); +// if (operator == Cage.Operator.DIVIDE) { +// while(values.remove(Integer.valueOf(0))) {} +// } + } + + public static int doit(List<Integer> values, Cage.Operator operator, Random ranObj) { + return new RandomCalcTree(values, ranObj, operator).rec(0, values.size()); + } + + public int rec(int start, int end) { + if (end - start <= 1) { + assert values.get(start) != 0; //TODO dbg + return values.get(start); + } + int split = ranObj.nextInt(start + 1, end); + return operator.perform(rec(start, split), rec(split, end)); + } +} diff --git a/src/com/patryk/mathdoku/randomGame/RandomGame.java b/src/com/patryk/mathdoku/randomGame/RandomGame.java new file mode 100644 index 0000000..a9c9c5b --- /dev/null +++ b/src/com/patryk/mathdoku/randomGame/RandomGame.java @@ -0,0 +1,63 @@ +package com.patryk.mathdoku.randomGame; + +import com.patryk.mathdoku.UserData; +import com.patryk.mathdoku.cageData.Cage; +import com.patryk.mathdoku.cageData.CageData; +import com.patryk.mathdoku.cageData.Cell; +import com.patryk.mathdoku.util.BoardPosVec; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Stream; + +public class RandomGame { + static final int MAX_CAGE_SIZE = 4; + public static UserData userData; //todo dbg + public static CageData randomGame(int seed, int size) { + /** + * random array + * partition into cages + * determine operators/operands + */ + BoardPosVec.setBoardWidth(size); + Random ranObj = new Random(seed); + UserData ud = randomUserData(ranObj, size); + CageData cd = randomCageData(ranObj, size, ud); + userData = ud; + return cd; + } + + public static UserData randomUserData(Random ranObj, int size) { + return RandomUserData.doit(size, ranObj); + } + + public static CageData randomCageData (Random ranObj, int width, UserData ud) { + //create blank cagedata + CageData cd = new CageData(width); + //determine cage boundaries + List<Cage> cl = RandomPartitionGrid.doit(cd.getData(), MAX_CAGE_SIZE, ranObj); + cd.setCageList(cl); + + + doOperatorsTargets(cd, ud, ranObj); + return cd; + } + + public static void doOperatorsTargets(CageData cd, UserData ud, Random ranObj) { + + //determine operators and operands + for(Cage cage: cd.getCages()) { + Cage.Operator op = Cage.Operator.values()[ranObj.nextInt(4)]; + //if (op == Cage.Operator.DIVIDE) op = Cage.Operator.ADD; + Function<Integer, Integer> f = (Integer cageMember )-> ud.getValueAtCell(Integer.valueOf(cageMember)); + List<Integer> cageValues = new ArrayList(cage.getMemberCells().stream().map(f).toList()); + int target = RandomCalcTree.doit(cageValues, op, ranObj); + cage.setOperator(op); + cage.setTarget(target); + } + } + +} + diff --git a/src/com/patryk/mathdoku/randomGame/RandomPartitionGrid.java b/src/com/patryk/mathdoku/randomGame/RandomPartitionGrid.java new file mode 100644 index 0000000..2b62dfb --- /dev/null +++ b/src/com/patryk/mathdoku/randomGame/RandomPartitionGrid.java @@ -0,0 +1,47 @@ +package com.patryk.mathdoku.randomGame; + +import com.patryk.mathdoku.cageData.Cage; +import com.patryk.mathdoku.cageData.Cell; +import com.patryk.mathdoku.util.BoardPosVec; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +class RandomPartitionGrid { + final Cell[] grid; + final int maxPartitionSize; + final Random ranObj; + + int cageId = 0; + List<Cage> cageList = new ArrayList<>(); + + public static List<Cage> doit(Cell[] grid, int maxPartitionSize, Random ranObj) { + var obj = new RandomPartitionGrid(grid, maxPartitionSize, ranObj); + obj.rec(new BoardPosVec(0, 0)); + return obj.cageList; + } + + public RandomPartitionGrid(Cell[] grid, int maxPartitionSize, Random ranObj) { + this.grid = grid; + this.maxPartitionSize = maxPartitionSize; + this.ranObj = ranObj; + } + + + private void rec(BoardPosVec pos) { + Cage cage = new Cage(); + cage.setMemberCells(FloodFunc.doit(grid, pos, maxPartitionSize, cageId, ranObj)); + cageList.add(cage); + cageId++; + for (int cellId : cage.getMemberCells()) { + BoardPosVec.forEveryCellAround(new BoardPosVec(cellId), (BoardPosVec varPos) -> { + if (grid[varPos.toIndex()].getCageId() == -1) { + rec(varPos); + } + }); + } + } + + +} diff --git a/src/com/patryk/mathdoku/randomGame/RandomUserData.java b/src/com/patryk/mathdoku/randomGame/RandomUserData.java new file mode 100644 index 0000000..df18285 --- /dev/null +++ b/src/com/patryk/mathdoku/randomGame/RandomUserData.java @@ -0,0 +1,56 @@ +package com.patryk.mathdoku.randomGame; + +import com.patryk.mathdoku.UserData; +import com.patryk.mathdoku.util.BoardPosVec; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +class RandomUserData { + UserData userData; + int size; + Random ranObj; + + public RandomUserData(int size, Random ranObj) { + this.size = size; + this.userData = new UserData(size); + this.ranObj = ranObj; + } + + public static UserData doit(int size, Random ranObj) { + var obj = new RandomUserData(size, ranObj); + obj.rec(new BoardPosVec(0,0)); + return obj.userData; + } + + private boolean rec(BoardPosVec pos) { + int r = pos.r; + int c = pos.c; + //todo simplify! + List<Integer> allDigits = new ArrayList<>(); + for(int i = 1; i <= size; i++) { + allDigits.add(i); + } + + Collections.shuffle(allDigits, ranObj); + + //remove row digits + for (int vcol = 0; vcol < c; vcol++) { + allDigits.remove(Integer.valueOf(userData.getValueAtCell(new BoardPosVec(r, vcol)))); + } + //remove column digits + for (int vrow = 0; vrow < r; vrow++) { + allDigits.remove(Integer.valueOf(userData.getValueAtCell(new BoardPosVec(vrow, c)))); + } + + + for (int digit: allDigits) { + userData.setValueAtCell(pos, digit); + if (r == size - 1 && c == size - 1 || rec(pos.next())) return true; + } + + return false; + } +} diff --git a/src/com/patryk/mathdoku/solver/CageSolver.java b/src/com/patryk/mathdoku/solver/CageSolver.java new file mode 100644 index 0000000..4dc7ca3 --- /dev/null +++ b/src/com/patryk/mathdoku/solver/CageSolver.java @@ -0,0 +1,95 @@ +package com.patryk.mathdoku.solver; + +import com.patryk.mathdoku.GameContext; +import com.patryk.mathdoku.UserData; +import com.patryk.mathdoku.cageData.Cage; +import com.patryk.mathdoku.errorChecking.RecursiveChecker; +import com.patryk.mathdoku.util.BoardPosVec; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CageSolver { + + public static Set<List<Integer>> doit(UserData userData, Cage cage) { + Set<List<Integer>> solutions = new HashSet<>(); + switch (cage.getOperator()) { + case ADD -> rec_add(userData, cage.getMemberCells(), solutions, 0, cage.getTarget()); + case MULTIPLY -> rec_multiply(userData, solutions, cage.getMemberCells(), 0, cage.getTarget()); + default -> rec_generic(userData, solutions, cage, 0); + } + return solutions; + } + + + + private static void rec_add(UserData userData, List<Integer> cells, Set<List<Integer>> solutions, int cageCellIndex, int remaining) { + int boardCellIndex = cells.get(cageCellIndex); + Set<Integer> allowedDigits = userData.getAllowedDigits(boardCellIndex); + if (cageCellIndex == cells.size() - 1) { + if (allowedDigits.contains(remaining)) { + userData.setValueAtCell(boardCellIndex, remaining); + solutions.add(cells.stream().map(userData::getValueAtCell).toList()); + userData.setValueAtCell(boardCellIndex, 0); + } + return; + } + + int upperLimit = Math.min(remaining - (cells.size() - cageCellIndex - 1), BoardPosVec.getBoardWidth()); + for(int i = upperLimit; i > 0; i--) { + if (allowedDigits.contains(i)) { + userData.setValueAtCell(boardCellIndex, i); + rec_add(userData, cells, solutions, cageCellIndex + 1, remaining - i); + + } + } + + userData.setValueAtCell(boardCellIndex, 0); + } + + private static void rec_multiply(UserData userData, Set<List<Integer>> solutions, List<Integer> cells, int cageCellIndex, int remaining) { + int boardCellIndex = cells.get(cageCellIndex); + Set<Integer> allowedDigits = userData.getAllowedDigits(boardCellIndex); + if (cageCellIndex == cells.size() - 1) { + if (allowedDigits.contains(remaining)) { + userData.setValueAtCell(boardCellIndex, remaining); + solutions.add(cells.stream().map(userData::getValueAtCell).toList()); + userData.setValueAtCell(boardCellIndex, 0); + } + return; + } + + for (int n : allowedDigits) { + if (remaining % n!= 0) continue; + + userData.setValueAtCell(boardCellIndex, n); + rec_multiply(userData, solutions, cells, cageCellIndex + 1, Math.floorDiv(remaining, n)); + } + userData.setValueAtCell(boardCellIndex, 0); + } + + + private static void rec_generic(UserData userData, Set<List<Integer>> solutions, Cage cage, int cageCellIndex) { + final List<Integer> cells = cage.getMemberCells(); + int boardCellIndex = cells.get(cageCellIndex); + Set<Integer> allowedDigits = userData.getAllowedDigits(boardCellIndex); + + for (int n : allowedDigits) { + userData.setValueAtCell(boardCellIndex, n); + + if (cageCellIndex == cells.size() - 1) { + var cageValues = cells.stream().map(userData::getValueAtCell).toList(); + if (RecursiveChecker.testSign(cageValues, cage.getTarget(), cage.getOperator())) { + solutions.add(cageValues); + return; + } + userData.setValueAtCell(boardCellIndex, 0); + continue; + } + + rec_generic(userData, solutions, cage, cageCellIndex + 1); + } + userData.setValueAtCell(boardCellIndex, 0); + } +} diff --git a/src/com/patryk/mathdoku/solver/Solver.java b/src/com/patryk/mathdoku/solver/Solver.java new file mode 100644 index 0000000..281d6b4 --- /dev/null +++ b/src/com/patryk/mathdoku/solver/Solver.java @@ -0,0 +1,75 @@ +package com.patryk.mathdoku.solver; + +import com.patryk.mathdoku.UserData; +import com.patryk.mathdoku.cageData.Cage; +import com.patryk.mathdoku.cageData.CageData; + +import java.util.List; +import java.util.Set; + +//todo check whole +public class Solver { + UserData userData; + List<Cage> cageList; + + public Solver(UserData userData, List<Cage> cageList) { + this.userData = userData; + this.cageList = cageList; + } + + + public static boolean solve(UserData userData, CageData cageData) { + var obj = new Solver(userData, cageData.getCageList()); + return obj.rec(0); + } + + private boolean rec(int cageIndex) { + //if (cageIndex == 12) return true; + Cage c = cageList.get(cageIndex); + Set<List<Integer>> cageSolutions = CageSolver.doit(userData, c); + if (cageSolutions.isEmpty()) { + return false; + } + if (cageIndex == cageList.size() - 1) { + applyCageSolution(userData, c.getMemberCells(), (List<Integer>) cageSolutions.toArray()[0]); + return true; + } + + for(List<Integer> solution: cageSolutions) { + applyCageSolution(userData, c.getMemberCells(), solution); + if (rec(cageIndex + 1)) return true; + } + + clearCageSolution(userData, c.getMemberCells()); + return false; + } + + public static void clearCageSolution(UserData userData, List<Integer> cells) { + + for(int i = 0; i < cells.size(); i++) { + userData.setValueAtCell(cells.get(i), 0); + } + } + + public static void applyCageSolution(UserData userData, List<Integer> cells, List<Integer> cageSolution) { + for(int i = 0; i < cells.size(); i++) { + userData.setValueAtCell(cells.get(i), cageSolution.get(i)); + } + } + +} + +/* rec(cageIndex) { + cage = cages[cageIndex] + solutions = get valid cage solutions + if solutions empty: + return false + if on last cage: + return first of solutions + + for solution in solutions: + if rec(cageIndex + 1) return true + + return false + + */ \ No newline at end of file diff --git a/src/com/patryk/mathdoku/solver/pseudocode.txt b/src/com/patryk/mathdoku/solver/pseudocode.txt new file mode 100644 index 0000000..3ba552b --- /dev/null +++ b/src/com/patryk/mathdoku/solver/pseudocode.txt @@ -0,0 +1,42 @@ +add: +proc rec(cell of cage, sum#remaining): + if on last cell and remaining not in forbidden digits: + write remaining into cell + return true + + upper limit = remaining - number of blanks in cage + 1 + for n in reverse(range(1, min(upper limit, N)): + if n not in forbidden digits: + write N into cell + if recurse(next cell in the cage) is true: + return true + + blank your spot + return false + +multiply: +proc rec(cell index, remaining): + if on last cell and remaining not in forbidden digits: + write remaining into cell + return true + + for every factor n of remaining less than limit: + if n not in forbidden digits: + write N into cell + if recurse(cell index + 1, remaining / factor) is true: + return true + + blank your spot + return false + +generic: +proc rec: + + for every number up to limit that is not in forbidden digits: + write N into cell + if on last cell: + record solution + return true + if recurse(cell index + 1) return true + + return false diff --git a/src/com/patryk/mathdoku/util/BoardPosVec.java b/src/com/patryk/mathdoku/util/BoardPosVec.java index c037583..3fa4bf1 100755 --- a/src/com/patryk/mathdoku/util/BoardPosVec.java +++ b/src/com/patryk/mathdoku/util/BoardPosVec.java @@ -1,5 +1,8 @@ package com.patryk.mathdoku.util; +import java.util.*; +import java.util.function.Consumer; + public class BoardPosVec { //private static GameContext gameContext = GameContext.getInstance(); //public static int width = GameContext.getBoardWidth(); @@ -32,6 +35,10 @@ public class BoardPosVec { c = index - r * boardWidth; } + public static int getBoardWidth() { + return boardWidth; + } + public BoardPosVec add(BoardPosVec other) { return new BoardPosVec(this.r + other.r, this.c + other.c); } @@ -64,6 +71,11 @@ public class BoardPosVec { c >= 0 && c < boardWidth; } + public static boolean isValid(int r, int c) { + return r >= 0 && r < boardWidth && + c >= 0 && c < boardWidth; + } + public int toIndex() { return boardWidth * r + c; } @@ -77,9 +89,43 @@ public class BoardPosVec { return new BoardPosVec(Util.pixelToBoard(this.r, boardWidth, pixelWidth), Util.pixelToBoard(this.c, boardWidth, pixelWidth)); } + public static void forEveryCellAround(BoardPosVec pos, Random ranObj, Consumer<BoardPosVec> func) { + BoardPosVec[] around = { + pos.add(new BoardPosVec(0,1)), + pos.add(new BoardPosVec(0,-1)), + pos.add(new BoardPosVec(1,0)), + pos.add(new BoardPosVec(-1,0)), + }; + + List<BoardPosVec> aroundObj = new ArrayList(List.of(around)); + if (ranObj != null) Collections.shuffle(aroundObj, ranObj); + + + for (var vec: aroundObj) { + if (!vec.isValid()) continue; + func.accept(vec); + } + } + + public static void forEveryCellAround(BoardPosVec pos, Consumer<BoardPosVec> func) { + forEveryCellAround(pos,null, func); + } @Override public String toString() { return String.format("(%d, %d)", r, c); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BoardPosVec that = (BoardPosVec) o; + return r == that.r && c == that.c; + } + + @Override + public int hashCode() { + return Objects.hash(r, c); + } } -- GitLab