Skip to content
Snippets Groups Projects
Commit 6ed84e5e authored by p9malino26's avatar p9malino26
Browse files

Add solver and random game generator

parent 1abe8388
No related branches found
No related tags found
No related merge requests found
Showing
with 706 additions and 38 deletions
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;
}
}
package com.patryk.mathdoku; 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.cageData.DataFormatException;
import com.patryk.mathdoku.solver.CageSolver;
import com.patryk.mathdoku.gui.GameUI; import com.patryk.mathdoku.gui.GameUI;
import com.patryk.mathdoku.gui.ManualGameInputDialog; 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.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
...@@ -16,7 +22,9 @@ import java.io.File; ...@@ -16,7 +22,9 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Random;
public class MathDoku extends Application { public class MathDoku extends Application {
GameUI gameUI; GameUI gameUI;
...@@ -65,6 +73,29 @@ public class MathDoku extends Application { ...@@ -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 //key event handler
...@@ -124,6 +155,8 @@ public class MathDoku extends Application { ...@@ -124,6 +155,8 @@ public class MathDoku extends Application {
gameUI.checkButton.addEventHandler(ActionEvent.ANY,(event) -> gameUI.gameGridView.showErrors()); gameUI.checkButton.addEventHandler(ActionEvent.ANY,(event) -> gameUI.gameGridView.showErrors());
gameUI.fileLoadButton.addEventHandler(ActionEvent.ANY, onFileLoadButtonPressed); gameUI.fileLoadButton.addEventHandler(ActionEvent.ANY, onFileLoadButtonPressed);
gameUI.textLoadButton.addEventHandler(ActionEvent.ANY, onManualInputButtonPressed); 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)); gameUI.setNumberButtonCallback((digit) -> gameUI.gameGridView.getInputHandler().injectNumberKey(digit));
...@@ -136,13 +169,15 @@ public class MathDoku extends Application { ...@@ -136,13 +169,15 @@ public class MathDoku extends Application {
} }
public boolean wantsToExit() { public boolean wantsToExit() {
if (shouldDisplayExitDialog()) { //todo debug
//show confirmation dialog
return gameUI.showConfirmExitDialog();
}
return true; return true;
// if (shouldDisplayExitDialog()) {
// //show confirmation dialog
//
//
// return gameUI.showConfirmExitDialog();
// }
// return true;
} }
public boolean shouldDisplayExitDialog() { public boolean shouldDisplayExitDialog() {
......
...@@ -5,9 +5,7 @@ import com.patryk.mathdoku.util.BoardPosVec; ...@@ -5,9 +5,7 @@ import com.patryk.mathdoku.util.BoardPosVec;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.*;
import java.util.LinkedList;
import java.util.List;
public class UserData { public class UserData {
public interface ChangeListener { public interface ChangeListener {
...@@ -98,6 +96,10 @@ public class UserData { ...@@ -98,6 +96,10 @@ public class UserData {
notifyListener(new ChangeListener.MultipleCellChange(false)); notifyListener(new ChangeListener.MultipleCellChange(false));
} }
public int getBoardWidth() {
return boardWidth;
}
public int getPopulationCount() { public int getPopulationCount() {
return populationCount; return populationCount;
} }
...@@ -122,17 +124,20 @@ public class UserData { ...@@ -122,17 +124,20 @@ public class UserData {
public void setValueAtCell(BoardPosVec cell, int value) { 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) { if (oldValue != value) {
data[cell.toIndex()] = value; data[index] = value;
if (oldValue == 0) if (oldValue == 0)
populationCount++; populationCount++;
if (value == 0) { if (value == 0) {
populationCount--; populationCount--;
} }
notifyListener(new ChangeListener.SingleCellChange(cell, oldValue, value)); notifyListener(new ChangeListener.SingleCellChange(new BoardPosVec(index), oldValue, value));
} }
} }
...@@ -167,6 +172,28 @@ public class UserData { ...@@ -167,6 +172,28 @@ public class UserData {
return builder.toString(); 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() { public void fill() {
for (int i = 0; i < fullSize;i ++) { for (int i = 0; i < fullSize;i ++) {
int val = (int)( 5 * Math.random()) + 1; int val = (int)( 5 * Math.random()) + 1;
......
package com.patryk.mathdoku.cageData; package com.patryk.mathdoku.cageData;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Cage { public class Cage {
int target; int target;
Cage.Operator operator; Cage.Operator operator = Operator.ADD;
int markedCell; int markedCell = 0;
private final List<Integer> memberCells; private List<Integer> memberCells = new ArrayList<>();
public Cage() {}
public Cage(int target, Cage.Operator operator, int markedCell, List<Integer> memberCells) { public Cage(int target, Cage.Operator operator, int markedCell, List<Integer> memberCells) {
this.target = target; this.target = target;
...@@ -39,6 +42,18 @@ public class Cage { ...@@ -39,6 +42,18 @@ public class Cage {
return s; 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 { public enum Operator {
ADD('+'), SUBTRACT('-'), MULTIPLY('x'), DIVIDE('÷'); ADD('+'), SUBTRACT('-'), MULTIPLY('x'), DIVIDE('÷');
...@@ -72,7 +87,7 @@ public class Cage { ...@@ -72,7 +87,7 @@ public class Cage {
case MULTIPLY: case MULTIPLY:
return operand1 * operand2; return operand1 * operand2;
case DIVIDE: case DIVIDE:
return Math.floorDiv(operand1, operand2); return Math.max(1, Math.floorDiv(operand1, operand2));
default: default:
return -1; return -1;
} }
......
...@@ -8,18 +8,34 @@ import java.util.*; ...@@ -8,18 +8,34 @@ import java.util.*;
public class CageData { public class CageData {
int width; int width;
Cell[] data; Cell[] data;
public List<Cage> getCageList() {
return cageList;
}
List<Cage> cageList = new ArrayList<Cage>(); List<Cage> cageList = new ArrayList<Cage>();
int cageCount; 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{ public CageData(String data) throws DataFormatException{
parseData(data); parseData(data);
} }
public Cell[] getData() {
return data;
}
public List<Cage> getCages() {return cageList; } public List<Cage> getCages() {return cageList; }
public boolean cellConnectsTo(BoardPosVec pos, Direction direction) { public boolean cellConnectsTo(BoardPosVec pos, Direction direction) {
Cell nextCell = getCellAt(pos.add(direction.vector)); Cell nextCell = getCellAt(pos.add(direction.vector));
...@@ -45,7 +61,7 @@ public class CageData { ...@@ -45,7 +61,7 @@ public class CageData {
if (size > 81) if (size > 81)
throw new DataFormatException(-1, "Too many cells! Max is 81."); throw new DataFormatException(-1, "Too many cells! Max is 81.");
initData(); initData();
cageCount = parser.parseData(new Scanner(rawData), data, cageList); parser.parseData(new Scanner(rawData), data, cageList);
} }
...@@ -71,17 +87,11 @@ public class CageData { ...@@ -71,17 +87,11 @@ public class CageData {
} }
public int getCageCount() { public int getCageCount() {
return cageCount; return cageList.size();
} }
}
class Cell {
private int cageID;
public Cell (int cageID) { public void setCageList(List<Cage> cageList) {
this.cageID = cageID; this.cageList = cageList;
} }
public int getCageId() {return cageID; }
} }
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;
}
}
...@@ -45,7 +45,7 @@ public class CageInfo { ...@@ -45,7 +45,7 @@ public class CageInfo {
populationCount++; populationCount++;
if (isFull()) { if (isFull()) {
int[] cageMemberData = getMembersOfCage(); int[] cageMemberData = getMembersOfCage();
isInvalid = !RecursiveSolver.testSign(cageMemberData, cage.getTarget(), cage.getOperator()); isInvalid = !RecursiveChecker.testSign(cageMemberData, cage.getTarget(), cage.getOperator());
} }
} }
......
package com.patryk.mathdoku.errorChecking; package com.patryk.mathdoku.errorChecking;
import com.patryk.mathdoku.ArrayConversions;
import com.patryk.mathdoku.cageData.Cage; import com.patryk.mathdoku.cageData.Cage;
public class RecursiveSolver { import java.util.List;
public class RecursiveChecker {
...@@ -35,14 +38,18 @@ public class RecursiveSolver { ...@@ -35,14 +38,18 @@ public class RecursiveSolver {
return false; return false;
} }
private RecursiveSolver(int target, Cage.Operator operator) { private RecursiveChecker(int target, Cage.Operator operator) {
this.target = target; this.target = target;
this.operator = operator; this.operator = operator;
this.permute = (operator == Cage.Operator.SUBTRACT || operator == Cage.Operator.DIVIDE); 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) { 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
...@@ -44,7 +44,7 @@ public class GameGridView { ...@@ -44,7 +44,7 @@ public class GameGridView {
} }
}; };
UserData.ChangeListener redrawGame = (data) -> { public void redrawGame() {
userDataDrawer.draw(); userDataDrawer.draw();
errorHighlighter.clearCanvas(); errorHighlighter.clearCanvas();
}; };
...@@ -88,7 +88,7 @@ public class GameGridView { ...@@ -88,7 +88,7 @@ public class GameGridView {
this.gameContext = gameContext; this.gameContext = gameContext;
//Drawer.init(gameContext.getBoardWidth(), pixelWidth); //Drawer.init(gameContext.getBoardWidth(), pixelWidth);
gameContext.getUserData().addChangeListener(redrawGame); gameContext.getUserData().addChangeListener(data -> redrawGame());
//link cage drawer to canvas and draw the cages //link cage drawer to canvas and draw the cages
......
...@@ -26,7 +26,7 @@ public class GameUI { ...@@ -26,7 +26,7 @@ public class GameUI {
private static final int MAX_WIDTH = 8; private static final int MAX_WIDTH = 8;
private Scene scene; private Scene scene;
GameContext gameContext; GameContext gameContext;
private boolean wonBefore = false; public boolean wonBefore = false;
public boolean hasWonBefore() { public boolean hasWonBefore() {
return wonBefore; return wonBefore;
...@@ -38,8 +38,10 @@ public class GameUI { ...@@ -38,8 +38,10 @@ public class GameUI {
public Button redoButton = new Button("Redo"); public Button redoButton = new Button("Redo");
public Button clearButton = new Button("Clear"); public Button clearButton = new Button("Clear");
public Button checkButton = new Button("Check"); public Button checkButton = new Button("Check");
public Button solveButton = new Button("Solve");
public Button fileLoadButton = new Button("Load game from file"); public Button fileLoadButton = new Button("Load game from file");
public Button textLoadButton = new Button("Load game from text input"); public Button textLoadButton = new Button("Load game from text input");
public Button ranGameButton = new Button("Random game");
public ComboBox<GameGridView.FontSize> fontSizeComboBox; public ComboBox<GameGridView.FontSize> fontSizeComboBox;
GameGridView.FontSize defaultFontSize = GameGridView.FontSize.MEDIUM; GameGridView.FontSize defaultFontSize = GameGridView.FontSize.MEDIUM;
...@@ -81,7 +83,7 @@ public class GameUI { ...@@ -81,7 +83,7 @@ public class GameUI {
undoRedoButtonManager = new UndoRedoButtonManager(undoButton, redoButton); undoRedoButtonManager = new UndoRedoButtonManager(undoButton, redoButton);
//initialize control pane //initialize control pane
controlPane = new VBox(); 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 //disable clear and check buttons
clearButton.setDisable(true); clearButton.setDisable(true);
......
...@@ -90,7 +90,7 @@ public class CageDrawer extends Drawer{ ...@@ -90,7 +90,7 @@ public class CageDrawer extends Drawer{
for (Cage c: data.getCages()) { for (Cage c: data.getCages()) {
drawCageText(new BoardPosVec(c.getMarkedCell()), c.toString()); drawCageText(new BoardPosVec(c.getMemberCells().get(0)), c.toString());
} }
} }
......
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);
}
});
}
}
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));
}
}
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);
}
}
}
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);
}
});
}
}
}
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;
}
}
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);
}
}
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
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
package com.patryk.mathdoku.util; package com.patryk.mathdoku.util;
import java.util.*;
import java.util.function.Consumer;
public class BoardPosVec { public class BoardPosVec {
//private static GameContext gameContext = GameContext.getInstance(); //private static GameContext gameContext = GameContext.getInstance();
//public static int width = GameContext.getBoardWidth(); //public static int width = GameContext.getBoardWidth();
...@@ -32,6 +35,10 @@ public class BoardPosVec { ...@@ -32,6 +35,10 @@ public class BoardPosVec {
c = index - r * boardWidth; c = index - r * boardWidth;
} }
public static int getBoardWidth() {
return boardWidth;
}
public BoardPosVec add(BoardPosVec other) { public BoardPosVec add(BoardPosVec other) {
return new BoardPosVec(this.r + other.r, this.c + other.c); return new BoardPosVec(this.r + other.r, this.c + other.c);
} }
...@@ -64,6 +71,11 @@ public class BoardPosVec { ...@@ -64,6 +71,11 @@ public class BoardPosVec {
c >= 0 && c < boardWidth; c >= 0 && c < boardWidth;
} }
public static boolean isValid(int r, int c) {
return r >= 0 && r < boardWidth &&
c >= 0 && c < boardWidth;
}
public int toIndex() { public int toIndex() {
return boardWidth * r + c; return boardWidth * r + c;
} }
...@@ -77,9 +89,43 @@ public class BoardPosVec { ...@@ -77,9 +89,43 @@ public class BoardPosVec {
return new BoardPosVec(Util.pixelToBoard(this.r, boardWidth, pixelWidth), Util.pixelToBoard(this.c, boardWidth, pixelWidth)); 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 @Override
public String toString() { public String toString() {
return String.format("(%d, %d)", r, c); 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);
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment