Sudoku - Breitere Ränder

xyss

Aktives Mitglied
Hallo,

Ich bin aktuell dabei, ein Programm für Sudokus zu schreiben (generieren, lösen, etc.). Mein Problem jedoch ist, dass ich hinsichtlich GUI's mehr oder weniger gar keine Erfahrung habe, und dementsprechend komplett überfordert bin. Mein wohl größtes Problem im Moment ist, dass ich nicht weiß, wie ich innerhalb eines JTable manche der Ränder dicker darstellen kann, damit die jeweiligen 3x3 Blöcke besser erkennbar sind.

Es soll also in etwa so aussehen:
http://www.sudoku-infos.de/img/sudoku_raster.gif

Wie kann ich das in Java bewerkstelligen?

Liebe Grüße


Edit:
Ein weiteres Problem, welches ich aktuell habe, ist, dass die Font-Einstellungen während der Eingabe nicht eingehalten werden (sobald man die Eingabe mit der Enter-Taste oder wechseln der Zelle beendet, wird es korrekt dargestellt). Das Problem ist also, dass Schriftart, Schriftgröße und Ausrichtung während der Eingabe den "Default"-Wert annehmen.
Siehe hier:
8iel46h8.png
 
Zuletzt bearbeitet:

kaoZ

Top Contributor
Was hast du den bisher ? und es gibt immer mehrere Möglichkeiten, du könntest entweder das ganze selber zeichnen, und oder mit Rahmen + Buttons arbeiten,

Die Frage ist wie weit kennst du dich mit Swing und dessen Layoutmanagern / der Java 2D API aus ?

Hier ein kleines Beispiel mit Strokes, ich denke du weißt worauf ich hinaus will,
du kannst das ganze natürlich auch wie eine Art TileMap aufbauen und die einzelnen Felder dann klickbar machen usw usw....

Java:
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;


@SuppressWarnings("serial")
public class StrokeExample extends JPanel{

	public static int WIDTH = 400;
	public static int HEIGHT = 400;
	
	private int width;
	private int height;
	private int numOfTiles;
	
	public StrokeExample(int numOfTiles){
		width = WIDTH / numOfTiles;
		height = HEIGHT / numOfTiles;
		setPreferredSize(new Dimension(WIDTH, HEIGHT));
	}
	
	private void paintRaster(Graphics g){
		Graphics2D g2 = (Graphics2D) g;
	
		g2.setStroke(new BasicStroke(5f));
		
		for (int row = 0; row < HEIGHT; row++) {
			for (int col = 0; col < WIDTH; col++) {
				
				g2.setStroke(new BasicStroke(5f));
				
				//vertical
				g2.drawLine(0, row * height, WIDTH, row * height);
				
				//horizontal
				g2.drawLine(col * width, 0, col * width, HEIGHT);
			}
		}
		
	}
	
	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		paintRaster(g);
	}
	
	public static void main(String[] args){
		JFrame f = new JFrame("StrokeExample");
		f.setDefaultCloseOperation(2);
		f.setContentPane(new StrokeExample(9));
		f.pack();
		f.setLocationRelativeTo(null);
		f.setVisible(true);
	}
}

Du müsstest dann dementsprechend den Stroke setzen wenn du eine Dickere Linie ziehen willst, hier ist , da es eben nur ein Beispiel ist alles dicker gezeichnet, und ohne double bufferering,
da ich nicht weiß wie weit du bist und wie de es aufgebaut hast kann ich sonst leider nicht viel dazu sagen ;)
 
Zuletzt bearbeitet:

xyss

Aktives Mitglied
Naja.. wie weit ich bin, lässt sich schnell sagen:

Ich hab Eclipse WindowBuilder herangezogen, einen JTable hingepackt, und dann noch Sachen wie Borders (außen rum BevelBorder) sowie RowSize eingestellt.. mit Strokes, der Java 2D API oder sonstigem habe ich leider noch nie gearbeitet.

Kann ich das Ganze auch auf irgendeine Weise bei einem JTable einbinden?

lg
 

kaoZ

Top Contributor
Du könntest eine unterklasse von JTable erstellen und eben selbst festlegen wie die Rahmen der einzelnen Zellen gezeichnet werden, insofern die klasse selbst die möglichkeit nicht bieten sollte, die machst du wie in meinem Beispiel indem du dessen paint(), bzw paintComponent() methode überschreibst.

BZw. einen eigenen TableCellRenderer schreiben, der dann festlegt , wie die Zellen der Tabelle gezeichnet werden, wobei ich mir da nicht ganz sicher bin.
 
Zuletzt bearbeitet:

kaoZ

Top Contributor
So kannst du z.B die Eigenschaften der Tabelle anpassen ,

Java:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;


@SuppressWarnings("serial")
public class StrokeExample extends JTable{

	private int width;
	private int height;
	private int rows;
	private int cols;
	
	public StrokeExample(int width, int height, int rows, int cols){
		
		this.width = width;
		this.height = height;
		this.rows = rows;
		this.cols = cols;
		
		DefaultTableModel model = new DefaultTableModel(rows, cols);
		
		setPreferredSize(new Dimension(width, height));   // größe setzen
		setRowHeight(height / rows);					  // zeilenhöhe ändern
		setTableHeader(null); 							  // header ausblenden
 		setGridColor(Color.RED);						  // farbe setzen
		setModel(model);								  // model setzen
	}
	
	@Override
	public void paintComponent(Graphics g){
		
		Graphics2D g2 = (Graphics2D) g;
		
		g2.setStroke(new BasicStroke(5f));
		
		super.paintComponent(g2);
	}
	
	public JPanel createPanel(){
		JPanel panel = new JPanel();
		
		panel.setPreferredSize(new Dimension(width,height));
		panel.add(this);
		
		return panel;
	}
	
	public static void main(String[] args){
		JFrame f = new JFrame("StrokeExample");
		f.setDefaultCloseOperation(2);
		f.setContentPane(new StrokeExample(400, 400, 9, 9).createPanel());
		f.pack();
		f.setLocationRelativeTo(null);
		f.setVisible(true);
	}
}

Dies ist natürlich nur ein simpes Beispiel , von den Dingen die du dann alle anpassen müsstest.
du müsstest auch , damit der Inhalt der Zellen nach dienen vorstellungen dargestellt wird, den CellRenderer , bzw CellEditor dementsprechend anpassen.
 
Zuletzt bearbeitet:

xyss

Aktives Mitglied
Wow...schonmal vorab ein riesiges Dankeschön!

Klare Ansagen, was gemacht werden muss, und auch ein funktionierendes Beispiel, an welchem man sich orientieren kann (in den meisten Foren findet man nur Verlinkungen zu irgendwelcher Literatur o.Ä., die einen als Anfänger nunmal überrumpelt), echt super! :)

Hab deine Stroke-Klasse ein wenig umgewandelt, da ich die Größe des Sudokus variabel gehalten habe, somit werden nun die dicken Linien nicht nur für 9x9, sondern auch für 4x4, 6x6, 8x8, 10x10 und 16x16 korrekt dargestellt :)

Hast du vielleicht auch eine Idee, wie ich das 2. Problem, welches ich in meinem Anfangspost geschildert habe, lösen kann? Hat das eventuell auch mit dem CellEditor / Renderer zu tun?

lg
 
Zuletzt bearbeitet:

kaoZ

Top Contributor
Zentrieren könntest du folgendermaßen, du holst dir den Renderer der Jlabel/TableColoumn und setzt dessen alignment auf CENTER :

getestet und funktioniert ;) abschnitt ist markiert.

Java:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;


@SuppressWarnings("serial")
public class StrokeExample extends JTable{

	private int width;
	private int height;
	private int rows;
	private int cols;
	
	public StrokeExample(int width, int height, int rows, int cols){
		
		this.width = width;
		this.height = height;
		this.rows = rows;
		this.cols = cols;
		
		DefaultTableModel model = new DefaultTableModel(rows, cols);

		//Inhalte Zentriert darstellen
		JLabel renderer = ((JLabel)getDefaultRenderer(Object.class));
        renderer.setHorizontalAlignment(SwingConstants.CENTER);
        setDefaultRenderer(JTable.class, (TableCellRenderer) renderer);
		
		
		setPreferredSize(new Dimension(width, height));   // größe setzen
		setRowHeight(height / rows);					  // zeilenhöhe ändern
		setTableHeader(null); 							  // header ausblenden
 		setGridColor(Color.RED);						  // farbe setzen
		setModel(model);							      // model setzen
	}
	
	@Override
	public void paintComponent(Graphics g){
		
		Graphics2D g2 = (Graphics2D) g;
		
		g2.setStroke(new BasicStroke(5f));
		
		super.paintComponent(g2);
	}
	
	public JPanel createPanel(){
		JPanel panel = new JPanel();
		
		panel.setPreferredSize(new Dimension(width,height));
		panel.add(this);
		
		return panel;
	}
	
	public static void main(String[] args){
		JFrame f = new JFrame("StrokeExample");
		f.setDefaultCloseOperation(2);
		f.setContentPane(new StrokeExample(400, 400, 9, 9).createPanel());
		f.pack();
		f.setLocationRelativeTo(null);
		f.setVisible(true);
	}
}
 
Zuletzt bearbeitet:

xyss

Aktives Mitglied
Das ändert leider nichts an dem Problem, dass es während der Eingabe falsch formatiert ist... ich habe als "Einstellungen" Zentral, Fett, eine andere Schriftart und Schriftgröße, das wird aber während der Eingabe ignoriert:

8iel46h8.png
 

xyss

Aktives Mitglied
Ein weiteres Problem, an welchem ich gerade festhänge (auch wenn es hierbei nicht um GUI's geht):

Meine Klasse, welche die Sudokus generiert und löst heißt GeneratorAndSolver. Nun ist folgendes mein Problem:
Java:
GeneratorAndSolver g = new GeneratorAndSolver();
int[][] s = ... //Sudoku in Form eines int-Array

g.setSudoku(s);
g.solve()
;

Ich erstelle also einen Generator/Solver und übergebe ihm ein int-Array, welches ein Sudoku darstellt. Die Methode solve() löst das Sudoku intern (der Generator besitzt ein Attribut
Java:
private int[][] sudoku
welches er löst). Nun wird jedoch, ohne dass ich das in irgend einer Weise angebe, in meinem hier beschriebenen Codeschnipsel der Array "s" mit verändert. Selbst wenn ich von s einen Clone erstelle, wird dieser mitverändert... kann ich das irgendwie verhindern?
Schließlich will ich ja nur im Generator das Sudoku lösen (und auf Anfrage mit getSudoku() zurückgeben), und nicht direkt den Array manipuliert bekommen.
 
Zuletzt bearbeitet:

kaoZ

Top Contributor
Zu deinem Problem mit dem Schriftstil, der breite oder der Darstellung, du musst raus finden welche klasse die Darstellung der Schrift ( während des bearbeitens ) in den Zellen zuständig ist und eben genau da ansetzen, ich vermute mal das der TableCellEditor da mit drinn hängt.

zu deinem anderem Problem :

auch da gibt es, wie immer, mehrere Wege:

entweder arbeitest du mit einem lokalen array, welchem du nur das übergebene zuweist, löst es und das gelöste returnst.

Oder du erstellst ein leeres array, löst und weißt dem leeren lokalen array die Lösungen zu und returnst dann.
Und zeichnest bei bedarf dann das gelöste array.

Auch hier gibt es natürlich noch mehrere Ansätze , beim erstellen einer Kopie , dann es durchaus passieren das die Kopie identisch ist mit dem Originalen Array, dementsprechend die Referenz auch auf das gleiche Objekt zeigt, und somit auf den gleichen Speicherbereich, sprich veränderst du die Kopie, veränderst du auch das Original, allerdings bin ich mir da auch nicht zu 100% Sicher, weil ich nicht weiß wie du kopierst und ob es eine tiefe Kopie ist oder nicht.


Hier noch ein kleines Beispiel wie ich ggf. die Methoden gestalten würde :

Java:
//with new empty array
public int[][] solve(int[][]arr){
	
	int rows = arr.length;
	int cols = arr[0].length;
		
	int[][] solved = new int[rows][cols];
		
	//....logic here.....
		
	return solved;
}

oder

Java:
public int[][] solve(int[][]arr){
			
	int[][] toSolve = arr;
			
	//.....logic here
			
			
	return toSolve;
}
 
Zuletzt bearbeitet:

xyss

Aktives Mitglied
Wo kann ich denn "nachschlagen", welche Klasse für die Darstellung der Schrift zuständig ist? Steht sowas in der Javadoc?

Danke für den Tipp mit den Arrays, musste die Methoden jetzt ein wenig umkrempeln hinsichtlich Rückgabe etc, aber funktioniert jetzt :)
 

xyss

Aktives Mitglied
Alles klar, danke :)

Ich habe mich mittlerweile weiter mit dem Generator befasst, und kam auf folgendes Problem:

Ein Sudoku muss eindeutig lösbar sein, damit man es ohne Raten lösen kann. Das heißt also, die Sudokus, welche generiert werden, sollten ebenfalls eindeutig lösbar sein.

Meine Überlegung war die folgende:

- Das Programm generiert ein vollständig ausgefülltes Sudoku (welches natürlich die Sudoku-Regeln einhält). Dieser Teil funktioniert problemlos.
- Das Programm löscht dann so viele Zahlen wie möglich, ohne die Eindeutigkeit zu verletzen. Da es bisher keine bekannten Kriterien gibt, wann ein Sudoku eindeutig lösbar ist, war meine Idee, dass nach jeder gelöschten Zahl überprüft wird, ob das Sudoku eindeutig lösbar ist. Das heißt, nach jeder gelöschten Zahl wird das Sudoku gelöst, und wenn mehrere Lösungen dabei rauskommen, heißt das, dass die Eindeutigkeit verletzt ist.

So, und an dem Punkt haperts! Der Algorithmus soll ein vorgegebenes Sudoku (mit einer gewissen Menge an leeren Feldern) wieder lösen. Und zwar soll der Algo ALLE Lösungen angeben. Ich hab das Gefühl, dass ich einen Elefanten auf der Leitung sitzen habe, jedenfalls gibt mein Algorithmus mir immer nur EINE Lösung an (selbst bei einem komplett leeren Sudoku).
Ich habe, um das zu überprüfen, einen System.out.println eingebaut. Der Text "Found solution" wird nur ein einziges Mal ausgegeben. -> nur eine Lösung

Kleine Info: Die -1 steht für ein leeres Feld, und die Methode check überprüft, ob die Zahl x an der Position i,j zu einem Sudoku-Regelbruch führen würde (also ob man sie einfügen darf, oder nicht)
this.dim ist die Dimension des Sudokus, im Standard-Fall also 9.

Java:
	public boolean backtrackingSolve(int i, int j, int[][] s) {
        if (i == 9) {
            i = 0;
            if (++j == 9){
            	System.out.println("Found solution");
            	return true;
            }
        }
        if (s[i][j] != -1) 
            return backtrackingSolve(i+1,j,s);

        for (int k = 1; k <= 9; k++) {
            if (check(i,j,k,s)) {
            	int[][] s2 = new int[this.dim][this.dim];	// Dieser Teil
            												//dient dazu
        		for (int m = 0; m < this.dim; m++){			//dass "s"
        			for (int n = 0; n < this.dim; n++){		//nicht veraendert
        				s2[m][n] = s[m][n];					//wird
        			}										//
        		}											//
        		
                s2[i][j] = k;
                if (backtrackingSolve(i+1,j,s2)){
                    return true;
                }
            }
        }
        s[i][j] = -1;
        return false;
    }
 

xyss

Aktives Mitglied
Desweiteren hab ich nun noch folgendes Problem:
Ich habe einen TableCellRenderer geschrieben, um die Zellen, die man selbst eingibt, blau zu färben. (Die Zellen, welche am Anfang leer sind, sind editierbar, während die vorgegebenen fest sind, ich habe ein dementsprechendes TableModel dafür)
Das funktioniert auch soweit.

Allerdings würde ich auch gerne hinzufügen, dass nach klick auf einen Button "Check" die blauen Elemente grün bzw rot gefärbt werden, je nachdem ob der Wert korrekt ist oder nicht. Da müsste ich ja dann irgendwie mein Sudoku dem TableCellRenderer übergeben, damit dieser die korrektheit überprüfen kann, oder nicht? Dass das Ganze erst bei Buttonklick durchgeführt werden soll, machts auch nicht einfacher..

Java:
import java.awt.Color;
import java.awt.Component;

import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

public class TableCellRenderer extends DefaultTableCellRenderer {


public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int col) {

    Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
    if (!table.isCellEditable(row,col)){
    	c.setForeground(Color.BLACK);
    }else{
    	c.setForeground(new Color(0,0,255,255)); //Format: (r,g,b,a),identisch mit Color.BLUE hier.
    }

    return c;
}
}
 

xyss

Aktives Mitglied
Das mit dem CellRenderer habe ich nun hinbekommen, jedoch komme ich bei meinem Backtracking nach wie vor nicht weiter...

lg
 

kaoZ

Top Contributor
Du müsstest eine Methode schreiben, welche eben genau das prüft, und einen Listener der die methode aufruft und das model dieser übergibt, wenn du den Listener als innere klasse deklarierst, kannst du das model, sofern es ein attribut der äußeren klasse ist direkt übergeben, sprich so in der art :


Java:
class OuterClass{

  DefaultTableModel model;


......

  void checkFields(DefaultTableModel model){...}

class MyListener implements ActionListener{

  public void actionPerformed(ActionEvent e){
       if(e.getSource() == btn){
              checkFields(this.model);
       }
  }
}
}

Da müsste ich ja dann irgendwie mein Sudoku dem TableCellRenderer übergeben, damit dieser die korrektheit überprüfen kann, oder nicht?


nein , der Renderer macht im Normalfall nichts anderes als eben den Inhalt zu zeichnen, die Logik was richtig und was falsch ist hat da nichts drin verloren, dies könnte die Klasse selbst oder eine Hilfsklasse erledigen.
 
Zuletzt bearbeitet:

xyss

Aktives Mitglied
Wieder einmal super, danke :)

Das Programm soll natürlich möglichst viel drauf haben, daher würde ich auch gerne die sogenannten Samurai-Sudokus implementieren:

Samurai-sudoku.png

Quelle: google


Das Generieren stelle ich mir eigentlich nicht wirklich schwer vor, es sind ja im Endeffekt 5 separate 9x9 Sudokus, welche teilweise identische Blöcke haben.
Das, was ich eher für problematisch halte, ist die grafische Darstellung.. ich bin mir noch nicht sicher, wie ich das bewerkstelligen soll.
Eine Idee war, 5 separate JTables zu erstellen (welche durch das Stroke schon das passende Raster haben), und dann das mittlere Sudoku-Feld "über" die anderen zu legen. Wäre das realisierbar, dass ein JTable teile des anderen überdeckt? Und wie wäre das dann mit der Funktionalität beim Eintragen von Werten, da ja an den überdeckte Stellen 2 Zellen übereinander liegen würden.

Oder könnte man solch eine spezifische "Tabelle" auch beim TableModel (oder sonst wo?) definieren?

lg
 
Zuletzt bearbeitet:

kaoZ

Top Contributor
Solange du es dir vorstellen kannst , ist es auch machbar ;)

Ich sehe hier genau 4 Besonderheiten, und das sind jeweils die 4 ecken des mittleren Sudoku's, da diese eben anscheinend mit den äußeren 4 agieren, bzw. diese mit einbezogen werden.

OOP-Ansatz wäre folgendes, du erstellst dir eine Klasse , welche ein 3x3 block repräsentiert, 9 davon zusammen ( in einer 3 x 3 Matrix) ergeben ein einzelnes, vollständiges soduku.

so kannst du frei entscheiden wie viele dieser du letzten Endes zusammensetzt, du musst dann natürlich dementsprechende Methoden schreiben, welche dann die jeweiligen Blöcke( bzw. das sudoku auswerten ( vertikal sowie horizontal)

du könntest dann , ggf. Methoden anbieten welche dir die einzelnen Blöcke returnen, und einem anderen Soduku ( 3x 3x3 blöcke ) hinzufügen und mit in die Auswertung einbeziehen.

Dies dann grafisch umzusetzen sollte das geringste Problem sein. :lol:

Ich hoffe du hast zumindest eine Ahnung was ich meine ^^
 
Zuletzt bearbeitet:

xyss

Aktives Mitglied
Vielen Dank für deine tolle Hilfe über die letzten Tage :)

Das Projekt ist nun soweit erledigt, daher markiere ich den Thread mal als "erledigt" :)
 

Ähnliche Java Themen


Oben