//============================================================================= // Copyright 2006-2010 Daniel W. Dyer // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //============================================================================= package org.uncommons.watchmaker.examples.sudoku; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.SpringLayout; import javax.swing.SwingUtilities; import org.uncommons.maths.random.DiscreteUniformGenerator; import org.uncommons.maths.random.MersenneTwisterRNG; import org.uncommons.maths.random.PoissonGenerator; import org.uncommons.maths.random.Probability; import org.uncommons.swing.SpringUtilities; import org.uncommons.swing.SwingBackgroundTask; import org.uncommons.watchmaker.examples.AbstractExampleApplet; import org.uncommons.watchmaker.framework.EvolutionEngine; import org.uncommons.watchmaker.framework.EvolutionObserver; import org.uncommons.watchmaker.framework.EvolutionaryOperator; import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.SelectionStrategy; import org.uncommons.watchmaker.framework.operators.EvolutionPipeline; import org.uncommons.watchmaker.framework.selection.TournamentSelection; import org.uncommons.watchmaker.framework.termination.TargetFitness; import org.uncommons.watchmaker.swing.AbortControl; import org.uncommons.watchmaker.swing.ProbabilityParameterControl; import org.uncommons.watchmaker.swing.SwingEvolutionObserver; import org.uncommons.watchmaker.swing.evolutionmonitor.StatusBar; /** * An evolutionary Sudoku solver. * @author Daniel Dyer */ public class SudokuApplet extends AbstractExampleApplet { private static final String[] BLANK_PUZZLE = {".........", ".........", ".........", ".........", ".........", ".........", ".........", ".........", "........."}; private static final String[] EASY_PUZZLE = {"4.5...9.7", ".2..9..6.", "39.6.7.28", "9..3.2..6", "7..9.6..3", "5..4.8..1", "28.1.5.49", ".7..3..8.", "6.4...3.2"}; private static final String[] MEDIUM_PUZZLE = {"....3....", ".....6293", ".2.9.48..", ".754...38", "..46.71..", "91...547.", "..38.9.1.", "1567.....", "....1...."}; private static final String[] HARD_PUZZLE = {"...891...", "....5.8..", ".....6.2.", "5....4..8", "49....67.", "8.13....5", ".6..8..9.", "..5.4.2.7", "...1.3.8."}; private static final String[][] PUZZLES = {EASY_PUZZLE, MEDIUM_PUZZLE, HARD_PUZZLE, BLANK_PUZZLE}; private SelectionStrategy<Object> selectionStrategy; private SudokuView sudokuView; private JButton solveButton; private JComboBox puzzleCombo; private JSpinner populationSizeSpinner; private AbortControl abortControl; private StatusBar statusBar; /** * Initialise and layout the GUI. * @param container The Swing component that will contain the GUI controls. */ @Override protected void prepareGUI(Container container) { sudokuView = new SudokuView(); container.add(createControls(), BorderLayout.NORTH); container.add(sudokuView, BorderLayout.CENTER); statusBar = new StatusBar(); container.add(statusBar, BorderLayout.SOUTH); sudokuView.setPuzzle(EASY_PUZZLE); } private JComponent createControls() { JPanel controls = new JPanel(new BorderLayout()); JPanel innerPanel = new JPanel(new SpringLayout()); innerPanel.add(new JLabel("Puzzle: ")); puzzleCombo = new JComboBox(new String[]{"Easy Demo (38 givens)", "Medium Demo (32 givens)", "Hard Demo (28 givens)", "Custom"}); innerPanel.add(puzzleCombo); puzzleCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent ev) { sudokuView.setPuzzle(PUZZLES[puzzleCombo.getSelectedIndex()]); } }); innerPanel.add(new JLabel("Selection Pressure: ")); ProbabilityParameterControl selectionPressure = new ProbabilityParameterControl(Probability.EVENS, Probability.ONE, 2, new Probability(0.85d)); selectionStrategy = new TournamentSelection(selectionPressure.getNumberGenerator()); innerPanel.add(selectionPressure.getControl()); innerPanel.add(new JLabel("Population Size: ")); populationSizeSpinner = new JSpinner(new SpinnerNumberModel(500, 10, 50000, 1)); innerPanel.add(populationSizeSpinner); SpringUtilities.makeCompactGrid(innerPanel, 3, 2, 0, 6, 6, 6); innerPanel.setBorder(BorderFactory.createTitledBorder("Configuration")); controls.add(innerPanel, BorderLayout.CENTER); controls.add(createButtonPanel(), BorderLayout.SOUTH); return controls; } private JComponent createButtonPanel() { JPanel buttonPanel = new JPanel(new FlowLayout()); solveButton = new JButton("Solve"); solveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { int populationSize = (Integer) populationSizeSpinner.getValue(); puzzleCombo.setEnabled(false); populationSizeSpinner.setEnabled(false); solveButton.setEnabled(false); abortControl.reset(); createTask(sudokuView.getPuzzle(), populationSize, (int) Math.round(populationSize * 0.05)).execute(); // Elite count is 5%. } }); buttonPanel.add(solveButton); abortControl = new AbortControl(); buttonPanel.add(abortControl.getControl()); abortControl.getControl().setEnabled(false); return buttonPanel; } /** * Helper method to create a background task for running the interactive evolutionary * algorithm. * @return A Swing task that will execute on a background thread and update * the GUI when it is done. */ private SwingBackgroundTask<Sudoku> createTask(final String[] puzzle, final int populationSize, final int eliteCount) { return new SwingBackgroundTask<Sudoku>() { @Override protected Sudoku performTask() { Random rng = new MersenneTwisterRNG(); List<EvolutionaryOperator<Sudoku>> operators = new ArrayList<EvolutionaryOperator<Sudoku>>(2); // Cross-over rows between parents (so offspring is x rows from parent1 and // y rows from parent2). operators.add(new SudokuVerticalCrossover()); // Mutate the order of cells within individual rows. operators.add(new SudokuRowMutation(new PoissonGenerator(2, rng), new DiscreteUniformGenerator(1, 8, rng))); EvolutionaryOperator<Sudoku> pipeline = new EvolutionPipeline<Sudoku>(operators); EvolutionEngine<Sudoku> engine = new GenerationalEvolutionEngine<Sudoku>(new SudokuFactory(puzzle), pipeline, new SudokuEvaluator(), selectionStrategy, rng); engine.addEvolutionObserver(new SwingEvolutionObserver<Sudoku>(new GridViewUpdater(), 100, TimeUnit.MILLISECONDS)); engine.addEvolutionObserver(statusBar); return engine.evolve(populationSize, eliteCount, new TargetFitness(0, false), // Continue until a perfect solution is found... abortControl.getTerminationCondition()); // ...or the user aborts. } @Override protected void postProcessing(Sudoku result) { puzzleCombo.setEnabled(true); populationSizeSpinner.setEnabled(true); solveButton.setEnabled(true); abortControl.getControl().setEnabled(false); } }; } /** * Evolution observer for displaying information at the end of * each generation. */ private class GridViewUpdater implements EvolutionObserver<Sudoku> { public void populationUpdate(final PopulationData<? extends Sudoku> data) { SwingUtilities.invokeLater(new Runnable() { public void run() { sudokuView.setSolution(data.getBestCandidate()); } }); } } /** * Entry point for running this example as an application rather than an applet. * @param args Program arguments (ignored). */ public static void main(String[] args) { new SudokuApplet().displayInFrame("Watchmaker Framework - Sudoku Example"); } }