Saltar al contenido

Creación de una GUI para un solucionador de Sudoku (completo con ejemplo ASCII)

Hola, encontramos la solución a tu pregunta, desplázate y la encontrarás a continuación.

Solución:

fuente antigua: http://i38.tinypic.com/5mieqa.png


Esto debería darle suficiente para comenzar. Simplemente agregue la lógica del captador para extraer los valores que ingresaron en los campos de texto.

Principal:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sudoku;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 *
 * @author nicholasdunn
 */
public class Main 

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
        // TODO code application logic here
        JFrame frame = new JFrame("");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();

        panel.add(new Board());
        panel.add(new JButton(">"));
        panel.add(new Board());
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    

NineSquare:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sudoku;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/**
 *
 * @author nicholasdunn
 */
public class NineSquare extends JPanel 

    // What direction in relation to the center square
    private JTextField nw,n,ne,e,se,s,sw,w,c;
    private JTextField[] fields = new JTextField[]
        nw,n,ne,e,se,s,sw,w,c
    ;
    private static final int BORDER_WIDTH = 5;

    public NineSquare(Color bgColor) 
        setLayout(new GridLayout(3,3));
        initGui();
        setBackground(bgColor);
    

    private void initGui() 
        for (int i = 0; i < fields.length; i++) 
            fields[i] = new JTextField(1);
            fields[i].setDocument(new NumericalDocument());
            add(fields[i]);
        
        setBorder(BorderFactory.createMatteBorder(BORDER_WIDTH,BORDER_WIDTH,BORDER_WIDTH,BORDER_WIDTH, Color.BLACK));
    

    public Dimension getPreferredDimension() 
        return new Dimension(100,100);
    

    public static class NumericalDocument extends PlainDocument 
        String numbers = "0123456789";
        @Override
        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException 
            if (getLength() == 0 && str.length() == 1 && numbers.contains(str)) 
                super.insertString(offs, str, a);
            
            else 
                Toolkit.getDefaultToolkit().beep();
            
        
    

Tablero:

package sudoku;

import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JPanel;

/**
 *
 * @author nicholasdunn
 */
public class Board extends JPanel 
    private NineSquare[] gridSquares = new NineSquare[9];
    private Color[] bgs = Color.blue.brighter(), Color.gray;
    public Board() 
        setLayout(new GridLayout(3,3));
        for (int i = 0; i < gridSquares.length; i++) 
            gridSquares[i] = new NineSquare(bgs[i%2]);
            add(gridSquares[i]);
        
    

La GUI del Sudoku

Ok, no pude evitarlo ... Aquí está mi intento. Está todo en un solo paquete:

  • GUI con todos los elementos que cumplen con la especificación (pregunta)
  • diseño flexible
  • sin dependencias externas: se utilizan diseños de swing estándar
  • validación de entrada (solo dígitos 0-9)
  • Arquitectura del controlador de vista de modelo
  • corredor de tareas en segundo plano (su GUI nunca se congela)
  • algunos métodos de depuración incorporados (salida de Sudoku como texto)
  • implementación ficticia: simula un cálculo de larga ejecución que muestra la capacidad de respuesta de la GUI

Hice todo lo posible para que el código fuera lo más legible que pude. Puede haber partes poco claras. Probablemente la parte de enhebrado no sea lúcida, pero si alguien encuentra esto de alguna utilidad, me complacerá describirlo mejor.

Así que mi objetivo era el uso más simple posible. Si observa las interfaces, es realmente difícil romper estas cosas (congelar la interfaz de usuario, obtener Null Pointer Exc, etc.) como un ejercicio para escribir API públicas. Puede que esta no sea la mejor implementación, pero es una de las mejores que escribí. 🙂

Espero eso ayude.

Así es como se ve:
Ejecución de ejemplo

(nota: los valores son aleatorios)

Uso

Todo lo que tienes que hacer es implementar la interfaz:

public interface SudokuImplementation 

    void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);

Simplemente haga todos los cálculos en este método y almacene los resultados con resultAcceptor.setSudokuResult()

Así es como se muestra realmente la GUI:

    SudokuImplementation sudokuImplementation =
        new YourSuperSudoku(); // <- your implementation

    SudokuView sudokuView = new SudokuView();
    sudokuView.setSudokuImplementation(sudokuImplementation);
    sudokuView.setVisible(true);

¡Y eso es todo!

Código

Todas las clases están en el paquete predeterminado: refactorice como desee. Aquí está la lista de ellos:

  1. SudokuView - GUI principal
  2. SudokuRun - corredor de ejemplo
  3. SudokuController: permite controlar la vista de manera segura
  4. SudokuImplementation - interfaz para la implementación de sudoku
  5. DummySudokuImplementation - implementación de ejemplo

1.SudokuView:

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.*;
/**
 * View which constructs every component and creates it's own controller.
 */
public class SudokuView extends JFrame 

    SudokuController controller;

    public void setSudokuImplementation(SudokuImplementation listener) 
        controller.setListener(listener);
    

    /** Creates new form NewJFrame */
    public SudokuView() 
        controller = new SudokuController();
        setTitle("Sudoku Solver 1.0");
        getContentPane().add(createCenterPanel(), BorderLayout.CENTER);
        getContentPane().add(createBottomPanel(), BorderLayout.SOUTH);
        setMinimumSize(new Dimension(600, 300));
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    

    private JPanel createBottomPanel() 
        JPanel bottomPanel = new JPanel(new GridBagLayout());
        JLabel leftLabel = createLabel("left");
        JLabel rightLabel = createLabel("right");

        controller.bindLeftLabel(leftLabel);
        controller.bindRightLabel(rightLabel);

        bottomPanel.add(leftLabel, getWholeCellConstraints());
        bottomPanel.add(new JSeparator(JSeparator.VERTICAL));
        bottomPanel.add(rightLabel, getWholeCellConstraints());

        bottomPanel.setBorder(new BevelBorder(BevelBorder.LOWERED));
        return bottomPanel;
    

    private JLabel createLabel(String text) 
        JLabel label = new JLabel(text);
        label.setHorizontalAlignment(JLabel.CENTER);
        return label;
    

    private JPanel createCenterPanel() 
        JPanel centerPanel = new JPanel(new GridBagLayout());
        centerPanel.add(createLeftPanel(), getWholeCellConstraints());
        centerPanel.add(createCenterButton(), getPreferredSizeConstraint());
        centerPanel.add(createRightPanel(), getWholeCellConstraints());
        return centerPanel;
    

    private GridBagConstraints getPreferredSizeConstraint() 
        // default will do
        return new GridBagConstraints();
    

    private JButton createCenterButton() 
        JButton goButton = new JButton(">");
        controller.bindCenterButton(goButton);
        return goButton;
    
    private static final Insets sixPixelInset = new Insets(6, 6, 6, 6);

    private JPanel createRightPanel() 
        JPanel rightPanel = create3x3Panel(6);
        for (int i = 0; i < 3; i++) 
            for (int j = 0; j < 3; j++) 
                JPanel panel2 = create3x3Panel(2);
                fillPanelWithNonEditable(panel2, i, j);
                rightPanel.add(panel2);

            
        
        rightPanel.setBorder(new EmptyBorder(sixPixelInset));
        return rightPanel;
    

    private JPanel createLeftPanel() 
        JPanel leftPanel = create3x3Panel(6);
        for (int i = 0; i < 3; i++) 
            for (int j = 0; j < 3; j++) 
                JPanel panel2 = create3x3Panel(2);
                fillPanelWithEditable(panel2, i, j);
                leftPanel.add(panel2);

            
        
        leftPanel.setBorder(new EmptyBorder(sixPixelInset));
        return leftPanel;
    

    private GridBagConstraints getWholeCellConstraints() 
        GridBagConstraints wholePanelCnstr = getPreferredSizeConstraint();
        wholePanelCnstr.fill = java.awt.GridBagConstraints.BOTH;
        wholePanelCnstr.weightx = 1.0;
        wholePanelCnstr.weighty = 1.0;
        return wholePanelCnstr;
    

    private void fillPanelWithEditable(JPanel panel, int majorRow, int majorColumn) 
        for (int minorRow = 0; minorRow < 3; minorRow++) 
            for (int minorColumn = 0; minorColumn < 3; minorColumn++) 
                final JFormattedTextField editableField = createEditableField();
                int column = majorColumn * 3 + minorColumn;
                int row = majorRow * 3 + minorRow;
                controller.bindLeftSudokuCell(row, column, editableField);
                panel.add(editableField);
            
        
    

    private void fillPanelWithNonEditable(JPanel panel, int majorRow, int majorColumn) 
        for (int minorRow = 0; minorRow < 3; minorRow++) 
            for (int minorColumn = 0; minorColumn < 3; minorColumn++) 
                final JFormattedTextField editableField = createNonEditableField();
                int column = majorColumn * 3 + minorColumn;
                int row = majorRow * 3 + minorRow;
                controller.bindRightSudokuCell(row, column, editableField);
                panel.add(editableField);
            
        
    

    private JPanel create3x3Panel(int gap) 
        final GridLayout gridLayout = new GridLayout(3, 3, 1, 1);
        gridLayout.setHgap(gap);
        gridLayout.setVgap(gap);
        JPanel panel = new JPanel(gridLayout);
        return panel;
    

    private JFormattedTextField createNonEditableField() 
        JFormattedTextField field = createEditableField();
        field.setEditable(false);
        field.setBackground(Color.WHITE); // otherwise non-editable gets gray
        return field;
    

    private JFormattedTextField createEditableField() 
        JFormattedTextField field = new JFormattedTextField();
        // accept only one digit and nothing else
        try 
            field.setFormatterFactory(new DefaultFormatterFactory(new MaskFormatter("#")));
         catch (java.text.ParseException ex) 
        
        field.setPreferredSize(new Dimension(16, 30));
        field.setHorizontalAlignment(javax.swing.JTextField.CENTER);
        field.setText(" ");
        field.setBorder(null);
        return field;
    

2. SudokuRun:

import java.awt.EventQueue;
import javax.swing.UIManager;

public class SudokuRun implements Runnable 

    public void run() 
        // ******************** here You can swap Your true implementation
        SudokuImplementation sudokuImplementation = new DummySudokuImplementation();
        // ***************************** *************** ********* **** ** *


        SudokuView sudokuView = new SudokuView();
        sudokuView.setSudokuImplementation(sudokuImplementation);
        sudokuView.setVisible(true);
    

    public static void main(String args[]) 
        tryToSetSystemLookAndFeel();
        EventQueue.invokeLater(new SudokuRun());
    

    private static void tryToSetSystemLookAndFeel() 
        try 
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
         catch (Exception ex) 
            System.out.println("Couldn't set LAF");
        
    

3. SudokuController:

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;

public class SudokuController 

    JLabel leftLabel, rightLabel;
    JFormattedTextField[][] leftSudoku, rightSudoku;
    JButton goButton;

    public SudokuController() 
        leftSudoku = new JFormattedTextField[9][9]; // standard sudoku size
        rightSudoku = new JFormattedTextField[9][9];
    

    void bindLeftLabel(JLabel label) 
        leftLabel = label;
    

    void bindRightLabel(JLabel label) 
        rightLabel = label;
    

    void bindLeftSudokuCell(final int row, final int column, JFormattedTextField field) 
        field.addPropertyChangeListener("value", new PropertyChangeListener() 

            // if user edits field than You could do something about it here
            public void propertyChange(PropertyChangeEvent evt) 
                if (evt.getNewValue() != null) 
                    String newValue = (String) evt.getNewValue();
                    userEditedValueAt(row, column, Integer.valueOf(newValue));
                
            
        );
        leftSudoku[row][column] = field;
    

    void userEditedValueAt(int row, int column, int value) 
        System.out.println("Value changed at row:" + row + ", column:" + column + " to " + value);
    

    void bindRightSudokuCell(int row, int column, JFormattedTextField field) 
        rightSudoku[row][column] = field;
    

    void spitOutSudokus() 
        System.out.println("Left:");
        System.out.println(getPrettyPrinted(leftSudoku));
        System.out.println("Right:");
        System.out.println(getPrettyPrinted(rightSudoku));
    

    private String getPrettyPrinted(JFormattedTextField[][] sudoku) 
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 9; i++) n");
        
        return sb.toString();
    

    void bindCenterButton(JButton goButton) 
        this.goButton = goButton;
        goButton.addActionListener(new ActionListener() 

            public void actionPerformed(ActionEvent e) 
                goButtonPressed();
            
        );
    
    SudokuImplementation listener;

    public void setListener(SudokuImplementation listener) 
        this.listener = listener;
    
    Thread backGroundThread;

    private void goButtonPressed() 
        if (listener != null) 
            if (backGroundThread == null 
    

    private Integer[][] getLeftValues() 
        Integer[][] values = new Integer[9][9];
        for (int i = 0; i < 9; i++) 
            for (int j = 0; j < 9; j++) 
                if (!leftSudoku[i][j].getText().equals(" ")) 
                    values[i][j] = Integer.valueOf(leftSudoku[i][j].getText());
                
            
        
        return values;
    

    public void setSudokuResult(final Integer[][] result) 
        // Any GUI interaction must be done on EDT
        // We don't want to block computation so we choose invokeLater
        // as opposed to invokeAndWait.
        EventQueue.invokeLater(new Runnable() 

            public void run() 
                for (int i = 0; i < 9; i++) 
                    for (int j = 0; j < 9; j++) 
                        rightSudoku[i][j].setValue(String.valueOf(result[i][j]));
                    
                
            
        );
    

    public void setSudokuTime(final String time) 
        EventQueue.invokeLater(new Runnable() 

            public void run() 
                leftLabel.setText("Running time: " + time);
            
        );
    

    public void setSudokuCompleted(final boolean completed) 
        EventQueue.invokeLater(new Runnable() 

            public void run() 

                rightLabel.setText("Completely Solved: " + completed);
                if (completed) 
                    spitOutSudokus();
                

            
        );
    

4. Implementación de Sudoku:

public interface SudokuImplementation 

    void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);

5. Implementación de DummySudoku:

import java.util.concurrent.TimeUnit;

/**
 * Simulates Sudoku solver. Demonstrates how to update GUI. The whole
 * implementation is constructed so GUI never freezes.
 */
class DummySudokuImplementation implements SudokuImplementation 

    public DummySudokuImplementation() 
    

    public void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor) 
        System.out.println("Long running computation simulation...");
        for (int i = 0; i < 50; i++) 
            resultAcceptor.setSudokuCompleted(false);
            resultAcceptor.setSudokuTime(String.valueOf(i * 50) + "ms");
            resultAcceptor.setSudokuResult(getRandomResult());
            waitSomeTime();
        
        resultAcceptor.setSudokuResult(leftSudokuValues);
        resultAcceptor.setSudokuCompleted(true);
        waitSomeTime();
        System.out.println("Done!");
    

    private void waitSomeTime() 
        try 
            TimeUnit.MILLISECONDS.sleep(50);
         catch (InterruptedException ex) 
        
    

    private Integer[][] getRandomResult() 
        Integer[][] randomResult = new Integer[9][9];
        for (int i = 0; i < 9; i++) 
            for (int j = 0; j < 9; j++) 
                randomResult[i][j] = (int) (Math.random() * 9);
            
        
        return randomResult;
    

Explicación

No digo que la forma en que lo hice sea la mejor. Me encantaría ver otra respuesta con, digamos, todas las vistas realizadas con MigLayout. Sería muy instructivo. Estaba aprendiendo Swing GUI cuando la implementación de Sun era solo una, por lo que prevaleció en mi estilo. Dicho esto, recomiendo consultar el curso corto Swing GUI de Sun. También incluye un caso de estudio sencillo. Después de leerlo, casi toda la parte de SudokuView debería quedar clara.

Separé el código para hacerlo más legible. Es por eso que el controlador es otra clase, no parte de la vista. La vista es solo para la construcción de widgets y diseño, pero para que sea simple (no para crear algunas clases más), también inicializo el controlador en ella.

El verdadero trabajo está en el controlador. Contiene los detalles más peludos ... El enhebrado también va allí, por lo que no es tan obvio lo que realmente hace. Implementé una clase Thread desde cero. Hay una alternativa: usar SwingWorker. Puede ser un cliché, pero déjelo claro: utilizo subprocesos para hacer que la GUI responda en cualquier momento. Sin el subproceso adecuado, toda la GUI se congelaría cuando se llevara a cabo el cálculo. Decidí hacerlo lo más fácil posible desde el punto de vista de la implementación de Sudoku, como actualizaciones incrementales sin bloqueo.

En cuanto al subproceso, es crucial saber qué código se ejecuta en qué subproceso. Cada acción disparada por el componente GUI se ejecuta en EDT (subproceso de despacho de eventos). Si realiza alguna tarea de larga duración en él, la GUI no responderá. Así que solo hago otro hilo (ver implementación de goButtonPressed()) e inícielo. Después de eso, EDT puede procesar cualquier otro evento sin bloquear.

Tu Sudoku se ejecuta en un hilo de fondo especial. Puede hacer lo que quiera, a menos que tenga que actualizar la GUI. Es casi seguro que lo hará, ya que ahí es donde van las actualizaciones parciales. Aquí hay una trampa: si llama a cualquier componente de la GUI directamente (establezca algunos valores), la GUI se congelará. Esta es una condición llamada violación de envío EDT. Toda la interacción con Swing debe realizarse en EDT para evitar bloqueos. ¿Cómo hacerlo? El EDT tiene especial cola de eventos solo por eso. Publicas un evento de actualización en la cola. El código EDT está constantemente atento a los eventos entrantes y actualiza la GUI en consecuencia. Básicamente, es una comunicación entre el hilo de fondo y EDT. Para publicar un evento en la cola, puede usar un método de utilidad especial diseñado solo para esto: EventQueue.invokeLater(new Runnable() /* here goes your GUI interaction */ );. Echa un vistazo a SudokuController métodos:

  • setSudokuResult ()
  • público void setSudokuTime ()
  • setSudokuCompleted ()

Ahí es donde se publican los eventos de actualización de la GUI.

No puedo entender cómo es posible que desee abandonar esa impresionante impresión ASCII.

Realmente debería echar un vistazo a los tutoriales que se proporcionan en @ http://download.oracle.com/javase/tutorial/uiswing/ y ver cómo funcionan los administradores de diseño.

Para los cuadros de texto, recomendaría usar JTextField. Aquí hay un código que puede usar para que solo acepten un dígito a la vez:

public class textBox extends JTextField implements KeyListener
    public textBox() 
        addKeyListener(this);       
    

    @Override
    public void keyPressed(KeyEvent e) 
    

    @Override
    public void keyReleased(KeyEvent e) 
    

    @Override
    public void keyTyped(KeyEvent ke) 

        //consume the event otherwise the default implementation will add it to the text
        ke.consume(); 

        if (Character.isDigit(ke.getKeyChar())) 
            this.setText(Character.toString(ke.getKeyChar()));
    

Si eres capaz, puedes dejar un post acerca de qué le añadirías a este ensayo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Respuestas a preguntas comunes sobre programacion y tecnología