initial commit

This commit is contained in:
Bene 2023-07-03 15:41:38 +02:00
commit 3e115c8c28
13 changed files with 754 additions and 0 deletions

1
easy.html Normal file

File diff suppressed because one or more lines are too long

1
easy_solve.html Normal file

File diff suppressed because one or more lines are too long

1
hard.html Normal file

File diff suppressed because one or more lines are too long

1
hard_solve.html Normal file

File diff suppressed because one or more lines are too long

395
src/Board.java Normal file
View File

@ -0,0 +1,395 @@
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public class Board {
Cell[] content = new Cell[81];
private String constructorString = "";
public Board(String s) {
char[] chars = s.toCharArray();
if(chars.length != 81)
throw new IllegalArgumentException("Der String muss 81 Werte enthalten!");
constructorString = s;
for(int i = 0; i<81;i++){
content[i] = new Cell(0, i);
}
for(int i = 0; i<81;i++){
int value = chars[i]-'0';
if(value > 0)
setValueAtPoint(value, i);
}
if(!checkConstraintsGlobally())
throw new RuntimeException("Sudoku ist ungültig!");
//MainGUI gui = new MainGUI(this);
}
Board onlyDisplaySolution()
{
char[] chars = constructorString.toCharArray();
for(int i = 0; i<81;i++){
if(chars[i]=='0')
{
chars[i]= (char) (content[i].value+ ((int) '0'));
}
else{
chars[i]='0';
}
}
return new Board(new String(chars));
}
public boolean trySolveAndValidate()
{
//findAndSolvePossibilities ist etwa 4,5-mal schneller als findAndSolvePossibilities
if(!findAndSolvePossibilities())
{
System.out.println("Keine eindeutigen Treffer durch letzte Möglichkeit... suche weiter in Gruppen...");
if(findAndSolvePossibilitiesInGroups()) {
System.out.println("Treffer durch Gruppensuche. Fange von vorne mit eindeutigen Treffern an...");
return trySolveAndValidate();
}
//findAndSolvePossibilitiesOnAxles();
//return false;
return findAndSolvePossibilitiesBruteForce();
}
return checkConstraintsGlobally();
}
boolean findAndSolvePossibilitiesBruteForce()
{
System.out.println("Jetzt hilft nur noch Brute Force...");
System.out.println(this);
for(Cell cell:getMissingFields())
{
ArrayList<Integer> temp = cell.getPossibilities();
if(temp.size() == 2)
{
Board p1 = cloneBoard();
int value = temp.get(0);
int location = cell.location;
p1.setValueAtPoint(value, location);
System.out.println("[BRUTE FORCE] setze "+value+" an Position: "+location);
boolean p1Solvable = false;
try{
p1Solvable = p1.trySolveAndValidate();
}catch (RuntimeException e){
//unser Guess war falsch
System.out.println(e.getMessage());
System.out.println("[BRUTE FORCE] "+value+" an Position: "+location+" war falsch. Lass uns die andere Möglichkeit probieren...");
System.out.println("[BRUTE FORCE] setze "+temp.get(1)+" an Position: "+location);
setValueAtPoint(temp.get(1), location);
return trySolveAndValidate();
}
if(p1Solvable)
{
System.out.println("[BRUTE FORCE] ERFOLGREICH!!!");
takeChangesFromBoard(p1);
return true;
}
System.out.println("[BRUTE FORCE] Die erste Brute Force Möglichkeit konnte nicht gelöst werden. Wir probieren es mit der zweiten Möglichkeit...");
//Weder gab es einen Fehler noch konnte das p1 gelöst werden, lass uns daher mit p2 weitermachen
Board p2 = cloneBoard();
p2.setValueAtPoint(temp.get(1), cell.location);
boolean p2Solvable = false;
try{
p2Solvable = p2.trySolveAndValidate();
}catch (RuntimeException e){
//unser Guess war falsch
System.out.println(e.getMessage());
System.out.println("[BRUTE FORCE] Die zweite Brute Force Möglichkeit war falsch. Wir probieren es mit dem nächsten 2er Feld...");
return false;
}
if(p2Solvable)
{
System.out.println("[BRUTE FORCE] ERFOLGREICH!!!");
takeChangesFromBoard(p2);
return true;
}
System.out.println("[BRUTE FORCE] Die zweite Brute Force Möglichkeit konnte nicht gelöst werden. Wir probieren es mit dem nächsten 2er Feld...");
}
}
return false;
}
private Board cloneBoard()
{
String temp = "";
for(Cell c :content)
{
temp += c.value;
}
return new Board(temp);
}
boolean findAndSolvePossibilitiesOnAxles()
{
System.out.println("[Achsenanalyse] ...not implemented yet...");
return false;
}
boolean findAndSolvePossibilitiesInGroups()
{
boolean changed = true;
boolean everChanged = false;
while(changed) {
changed = false;
//Alle Gruppen der fehlenden Felder durch iterieren
for(Group group:getAllGroupsOfMissingFields()) {
int[] hints = new int[10];
int[] hintsAtLocation = new int[10];
//Alle Felder der Gruppe durch iterieren
for(int locationInGroup: group.map)
{
Cell cell = content[locationInGroup];
//nur wenn das Feld nicht gefüllt ist betrachten
if(cell.value == 0) {
//Iteriere durch alle Möglichkeiten des Feldes
for (Integer value :cell.getPossibilities()) {
hints[value]++;
hintsAtLocation[value] = cell.location;
}
}
}
//Durch Ergebnis iterieren und schauen, ob es Hints nur einmal gibt
for(int i = 1; i< hints.length; i++){
if(hints[i]==1)
{
int location = hintsAtLocation[i];
setValueAtPoint(i, location);
//System.out.println("[GRUPPENSUCHE] Setze "+i+" an Position: "+location);
changed = true;
everChanged = true;
}
}
}
if(!changed)
return everChanged;
}
return true;
}
boolean findAndSolvePossibilities()
{
ArrayList<Cell> missingFields = getMissingFields();
if(missingFields.size()==0)
return true;
boolean changed = true;
while(changed) {
changed = false;
for (Cell index : missingFields) {
ArrayList<Integer> possibilities = index.getPossibilities();
if(possibilities.size()==1)//Es gibt nur noch eine Möglichkeit
{
int value = possibilities.get(0);
index.setValue(value);
//System.out.println("[LETZTER HINWEIS] Setze "+value+" an Position: "+index.location);
removeUnnecessaryPossibility(index.location,value);
changed = true;
missingFields.remove(index);
if(missingFields.size()==0)
return true;
//das Herausnehmen erzeugt eine ConcurrentModificationException in der For-Schleife,
//wenn man diese nicht verlässt
break;
}
}
if(!changed)
return false;
}
return true;
}
ArrayList<Cell> getMissingFields()
{
ArrayList<Cell> temp = new ArrayList<>();
for (Cell index : content) {
if(index.value == 0)
{
temp.add(index);
}
}
return temp;
}
public void removeUnnecessaryPossibility(int location, int value){
Group column = Group.getColumnAtLocation(location);
Group row = Group.getRowAtLocation(location);
Group nona = Group.getNonaAtLocation(location);
Set<Integer> set = new HashSet<Integer>();
for(Integer i: column.map)
{
set.add(i);
}for(Integer i: row.map)
{
set.add(i);
}for(Integer i: nona.map)
{
set.add(i);
}
for(Integer i: set)
{
content[i].deletePossibility(value);
}
}
public void setValueAtPoint(int value, int location)
{
if(location > 80 || location < 0)
throw new IllegalArgumentException("Die Location muss zwischen 0 und 80 liegen");
if(value > 9 || value < 1)
throw new IllegalArgumentException("Die Wert muss zwischen 1 und 9 liegen");
if(content[location].value != 0)
throw new RuntimeException("In Feld "+location+" existiert schon eine Zahl ("+content[location].value+")!");
content[location].setValue(value);
if(!checkConstraints(location))
throw new RuntimeException("Feld "+location+" kann nicht mit "+value+" gefüllt werden. Sudoku-Fehler!");
removeUnnecessaryPossibility(location, value);
}
public boolean checkConstraints(int location)
{
Group column = Group.getColumnAtLocation(location);
Group row = Group.getRowAtLocation(location);
Group nona = Group.getNonaAtLocation(location);
return checkConstraintsOfGroup(column) && checkConstraintsOfGroup(row) && checkConstraintsOfGroup(nona);
}
public boolean checkConstraintsGlobally()
{
for(int index: Group.getAllNonas().map)
{
if(!checkConstraintsOfGroup(Group.getNonaAtLocation(index)))
return false;
}
for(int index: Group.getAllColums().map)
{
if(!checkConstraintsOfGroup(Group.getColumnAtLocation(index)))
return false;
}
for(int index: Group.getAllRows().map)
{
if(!checkConstraintsOfGroup(Group.getRowAtLocation(index)))
return false;
}
return true;
}
public boolean checkConstraintsOfGroup(Group input){
boolean[] duplicates = new boolean[10];
for (int i = 0; i < 9; i++) {
int value = content[input.map[i]].value;
if(value == 0)
continue;
if(duplicates[value])
return false;
else
duplicates[value] = true;
}
return true;
}
public String toString()
{
String temp = "";
for(int i = 0; i<81;i++){
if(i % 27 == 0)
temp += "\n=======================\n";
else if(i%9==0)
temp += "\n";
else if(i%3==0)
temp += "|| ";
temp += content[i]+" ";
}
return temp;
}
public void getContentFromArray(int[] array)
{
if(array.length != 81)
throw new IllegalArgumentException("Das Array muss 81 Werte enthalten!");
}
void toHtml(String filename)
{
String style = "<link rel='stylesheet'' href='styles.css'>";
String output = style+getSudokuHtml(0);
output += "<br>"+getSudokuHtml(90);
output += "<br>"+getSudokuHtml(180);
output += "<br>"+getSudokuHtml(270);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename+".html"))) {
writer.write(output);
System.out.println("File successfully written.");
} catch (IOException e) {
e.printStackTrace();
System.out.println("Error writing to the file.");
}
}
private String getSudokuHtml(int deg)
{
String output = "<div class='Sudoku'>";
int[] nonaIds = Group.getAllNonas().map;
for (int j = 0; j < nonaIds.length; j++) {
if (j == 0) {
output += "<div class='NonaRow'>";
}
if (j == 3 || j == 6) {
output += "</div>";
output += "<div class='NonaRow'>";
}
int nonaId = nonaIds[j];
Group g = Group.getNonaAtLocation(nonaId);
Cell[] c = new Cell[9];
output += "<div class='Nona'>";
for (int i = 0; i < 9; i++) {
if (i == 0) {
output += "<div class='CellRow'>";
}
if (i == 3 || i == 6) {
output += "</div>";
output += "<div class='CellRow'>";
}
output += "<div class='Cell' style='transform: rotate("+deg+"deg)'>";
int value = content[g.map[i]].value;
if(value > 0)
output += value;
output += "</div>";
if (i == 8) {
output += "</div>";
}
}
output += "</div>";
if (j == 8) {
output += "</div>";
}
}
output += "</div>";
return output;
}
private HashSet<Group> getAllGroupsOfMissingFields()
{
ArrayList<Cell> missingFields = getMissingFields();
HashSet<Group> temp = new HashSet<>();
//Alle unausgefüllten Felder durch iterieren
for (Cell index : missingFields) {
//Alle Gruppen am fehlenden Feld durch iterieren
temp.addAll(Group.getAllGroupsAtLocation(index.location));
}
return temp;
}
private void takeChangesFromBoard(Board master)
{
for (int i = 0; i < 81; i++) {
if(content[i].value != master.content[i].value)
{
content[i].setValue(master.content[i].value);
}
}
}
}

62
src/Cell.java Normal file
View File

@ -0,0 +1,62 @@
import java.util.ArrayList;
import java.util.HashMap;
public class Cell {
int value;
int location;
private int possibilitiesCount = 9;
Controller control;
boolean[] possibilities = new boolean[]{false, true, true, true, true, true, true, true, true, true};
public Cell(int value, int location)
{
this.value = value;
this.location = location;
control = new Controller(this);
}
public void deletePossibility(int value)
{
if(possibilities[value]) {
possibilities[value] = false;
possibilitiesCount--;
if (possibilitiesCount == 0)
throw new RuntimeException("Feld hat keine Möglichkeit mehr! Fehler in der Ausführung!");
control.update();
}
}
public void setValue(int value){
this.value = value;
possibilities = new boolean[10];
//System.out.println("Setze "+value);
control.update();
}
public HashMap<Integer, Boolean> getAllPossibilities() {
HashMap<Integer, Boolean> temp = new HashMap<>();
for(int i= 1;i<possibilities.length;i++)
{
temp.put(i, possibilities[i]);
}
return temp;
}
public ArrayList<Integer> getPossibilities() {
ArrayList<Integer> temp = new ArrayList<>();
for(int i= 1;i<possibilities.length;i++)
{
if(possibilities[i])
temp.add(i);
}
return temp;
}
public String toString()
{
if(this.value > 0)
return value+"";
return " ";
}
}

50
src/CellGUI.java Normal file
View File

@ -0,0 +1,50 @@
import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
public class CellGUI extends JPanel {
Cell content;
public CellGUI(Cell content)
{
this.content = content;
this.content.control.linkGUI(this);
setBorder(BorderFactory.createLineBorder(Color.gray));
setSize(100,100);
//setBackground(getRdnColor());
//setBackground(Color.getHSBColor(0,0,(float) 0.95));
//setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
draw();
}
static public Color getRdnColor()
{
return Color.getHSBColor((float) Math.random(),(float) Math.random(),(float) Math.random());
}
public void draw()
{
this.removeAll();
if(content.value == 0) {
setLayout(new GridLayout(3, 3));
HashMap<Integer, Boolean> map = content.getAllPossibilities();
map.forEach((key, value) -> {
String t = value?key+"":"";
JLabel j = new JLabel(t+"");
j.setHorizontalAlignment(JLabel.CENTER);
j.setVerticalAlignment(JLabel.CENTER);
j.setForeground(Color.gray);
//j.setFont(new Font("sans-serif", Font.ITALIC, 8));
add(j);
});
}
else {
JLabel j = new JLabel(content.value+"");
j.setFont(new Font("sans-serif", Font.PLAIN, 32));
j.setPreferredSize(new Dimension(100, 100));
j.setHorizontalAlignment(JLabel.CENTER);
add(j);
}
}
}

21
src/Controller.java Normal file
View File

@ -0,0 +1,21 @@
import java.awt.*;
public class Controller {
private Cell modell;
private CellGUI view;
public Controller(Cell modell)
{
this.modell = modell;
}
public void linkGUI(CellGUI view)
{
this.view = view;
}
public void update()
{
if(view != null)
view.draw();
}
}

101
src/Group.java Normal file
View File

@ -0,0 +1,101 @@
import java.util.ArrayList;
public class Group{
int[] map = new int[9];
public Group(int [] map)
{
if(map.length != 9)
throw new IllegalArgumentException("Group must contain 9 index values. Not "+map.length+"!");
this.map = map;
//System.out.println("Hash Value: "+this.hashCode());
//System.out.println("Test");
}
public static Group getAllNonas()
{
return new Group(new int[]{0,3,6,27,30,33,54,57,60});
}
public static Group getAllColums()
{
return new Group(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8});
}
public static Group getAllRows()
{
return new Group(new int[]{0,9,18,27,36,45,54,63,72});
}
public static ArrayList<Group> getAllGroupsAtLocation(int location)
{
ArrayList<Group> temp = new ArrayList<>();
temp.add(getColumnAtLocation(location));
temp.add(getRowAtLocation(location));
temp.add(getNonaAtLocation(location));
return temp;
}
public static Group getRowAtLocation(int location)
{
int[] temp = new int[9];
while(location%9>0)
{
location--;
}
for(int i = 0;i<9;i++)
{
temp[i]=location+i;
}
return new Group(temp);
}
public static Group getColumnAtLocation(int location)
{
int rowStart = location%9;
int[] temp = new int[9];
int counter = location%9;
int index = 0;
while (counter < 81)
{
temp[index] = counter;
index++;
counter = counter+9;
}
return new Group(temp);
}
public static Group getNonaAtLocation(int location) {
while(location%3>0)
location--;
int modY = location/9%3;
location=location-modY*9;
return new Group(new int[]{location, location+1, location+2, location+9, location+10, location+11, location+18, location+19, location+20});
}
public String toString()
{
String temp = "";
for (int location:map) {
temp += location+" ";
}
return temp;
}
@Override
public boolean equals(Object group) {
if (this == group) return true;
if (group == null || getClass() != group.getClass()) return false;
Group g = (Group) group;
for (int i = 0; i < 9; i++) {
if(map[i] != g.map[i])
return false;
}
return true;
}
@Override
public int hashCode() {
int temp = 1;
for (int i = 1; i < 10; i++) {
temp += 10*i*map[i-1];
}
return temp;
}
}

27
src/Main.java Normal file
View File

@ -0,0 +1,27 @@
public class Main {
public static void main (String[] args) {
Board easy = new Board("042915000008407003097080254010000538000508162680000009920800305001009700070200001"); // 723.600
Board medi = new Board("000068903010302080000000050800900040300800700256473890130000000000000060704019000"); // 1.414.400
Board hard = new Board("085700000900000003004900160700000091400000008010000500500600480609831050000049000"); // 2.508.800
Board expe = new Board("000004082000002009000731000400300000020050000800000506000000070071000200003040010"); // 4.981.600
//Board evil = new Board("010050000000007200508900004000090050406500008300000000030600000601020400090000001");
Board nigh = new Board("000000060000006003034080200000001005002900030006500410029000000051000000003010084"); //13.816.000
hard.toHtml("hard");
run(hard);
hard.onlyDisplaySolution().toHtml("hard_solve");
}
static void run(Board b)
{
MainGUI gui = new MainGUI(b);
gui.start();
System.out.println(b);
//b2.setValueAtPoint(3, 1);
long startTime = System.nanoTime();
System.out.println(b.trySolveAndValidate()?"Lösen war erfolgreich :-)":"Lösen nicht möglich!");
long stopTime = System.nanoTime();
System.out.println(stopTime - startTime);
}
}

54
src/MainGUI.java Normal file
View File

@ -0,0 +1,54 @@
import javax.swing.*;
import java.awt.*;
public class MainGUI extends Thread {
JFrame meinFrame = new JFrame("My AWT");
Board b;
public MainGUI(Board b)
{
this.b = b;
}
public void run(){
meinFrame.setSize(900,900);
draw();
meinFrame.setVisible(true);
}
public void draw()
{
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3, 3));
meinFrame.add(panel);
int[] nonaIds = Group.getAllNonas().map;
for (int nonaId : nonaIds) {
Group g = Group.getNonaAtLocation(nonaId);
Cell[] c = new Cell[9];
for (int i = 0; i < 9; i++) {
c[i]=b.content[g.map[i]];
}
panel.add(new NonaGUI(c, CellGUI.getRdnColor()));
}
}
public void drawCells(JPanel panel)
{
for (int i = 0; i < b.content.length; i++) {
CellGUI c = new CellGUI(b.content[i]);
//meinFrame.add(c);
JLabel label = new JLabel (i+"", JLabel.CENTER);
// Die vertikale Ausrichtung des JLabels setzen wir auf "TOP"
label.setVerticalAlignment(JLabel.TOP);
// Die relative Ausrichtung des Textes zum Icon setzen wir auf "LEFT"
label.setHorizontalTextPosition(JLabel.LEFT);
// Wir fügen das JLabel unserem Dialog hinzu
panel.add(c);
}
}
}

20
src/NonaGUI.java Normal file
View File

@ -0,0 +1,20 @@
import javax.swing.*;
import java.awt.*;
import java.util.Stack;
public class NonaGUI extends JPanel {
public NonaGUI(Cell[] nona, Color background)
{
setBackground(background);
setLayout(new GridLayout(3, 3));
setBorder(BorderFactory.createLineBorder(Color.black));
for (Cell c: nona) {
// CellGUI cellGUI = new CellGUI(c);
// c.control.linkGUI(cellGUI);
add(new CellGUI(c));
}
}
}

20
styles.css Normal file
View File

@ -0,0 +1,20 @@
.Sudoku{
display: flex;
flex-direction: column;
}
.NonaRow,.CellRow{
display: flex;
}
.Nona{
border: 2px solid black;
}
.Cell{
padding: 5px;
width: 2em;
height: 2em;
display: flex;
justify-content: center;
align-items: center;
font-size: 150%;
border: 1px solid gray;
}