org.exolin.sudoku.solver.Sudoku.java Source code

Java tutorial

Introduction

Here is the source code for org.exolin.sudoku.solver.Sudoku.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package org.exolin.sudoku.solver;

import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.BiFunction;

/**
 *
 * @author Thomas
 */
public class Sudoku {
    /**
     * Beschreibt, ob die mglichen Zahlen berechnet sind.
     */
    public enum PossibleNumersCalculationState {
        /**
         * noch keine Berechnung durchgefhrt (nachdem {@link #clean()}
         * aufgerufen wurde)
         */
        /**
        * noch keine Berechnung durchgefhrt (nachdem {@link #clean()}
        * aufgerufen wurde)
        */
        UNCALCULATED,

        /**
         * Whrend mit {@link #clean()} der Zustand zu {@link #UNCALCULATED}
         * bergefhrt wird
         */
        REMOVING_CALCULATED_VALUES,

        /**
         * Whrend {@link #recalculate()} luft
         */
        CALCULATING,

        /**
         * Wenn die mglichen Zahlen berechnet sind
         */
        CALCULATED,

        /**
         * Wenn eine {@link InconsistenceException} aufgetreten ist
         */
        INCONSISTENT
    }

    private SudokuListener listener = SudokuListener.NONE;
    private final Block<CellBlock> blocks = new Block<>(pos -> new CellBlock(this, pos));
    private PossibleNumersCalculationState state = PossibleNumersCalculationState.CALCULATED;

    private void setState(PossibleNumersCalculationState state) {
        this.state = state;
        listener.onStateChanged(state);
    }

    public void setListener(SudokuListener listener) {
        this.listener = listener;
    }

    void onNumberKnown(CellBlock sourceBlock, SudokoCell sourceCell, int number) throws InconsistenceException {
        for (CellBlock b : blocks.getRow(sourceBlock.getPosition().getRow())) {
            if (b != sourceBlock)
                b.onNumberAppearsInRow(sourceCell, sourceCell.getPosition().getRow(), number);
        }

        for (CellBlock b : blocks.getColumn(sourceBlock.getPosition().getColumn())) {
            if (b != sourceBlock)
                b.onNumberAppearsInColumn(sourceCell, sourceCell.getPosition().getColumn(), number);
        }

        listener.onNumberKnown(sourceBlock, sourceCell, number);
    }

    void onNumberUnset(CellBlock block, SudokoCell cell) {
        listener.onNumberUnset(block, cell);
    }

    public CellBlock getBlock(Position position) {
        CellBlock block = blocks.get(position);
        assert block.getPosition().equals(position);
        return block;
    }

    public void setGivenNumber(Position blockPosition, Position cellPosition, int number) {
        getBlock(blockPosition).getCell(cellPosition).setGivenNumber(number);
    }

    void onPossibleNumberRemoved(CellBlock block, SudokoCell cell, int number) {
        listener.onPossibleNumbersChanged(block, cell, number, SudokuListener.ChangeType.REMOVED);
    }

    /**
     * Entfernt alle berechneten Werte
     */
    public void clean() {
        setState(PossibleNumersCalculationState.REMOVING_CALCULATED_VALUES);
        for (CellBlock block : blocks)
            block.removeCalculated();
        setState(PossibleNumersCalculationState.UNCALCULATED);
    }

    void recalculate() throws InconsistenceException {
        clean();
        setState(PossibleNumersCalculationState.CALCULATING);
        for (CellBlock block : blocks)
            block.recalculate();
        setState(PossibleNumersCalculationState.CALCULATED);
    }

    class Cross extends AbstractList<SudokoCell> {
        private final int outerBlockIndex, outerCellIndex;
        private final BiFunction<Integer, Integer, Position> getPosition;

        public Cross(int outerIndex, BiFunction<Integer, Integer, Position> getPosition) {
            this.outerBlockIndex = outerIndex / 3;
            this.outerCellIndex = outerIndex % 3;
            this.getPosition = getPosition;
        }

        Position getBlockPosition(int innerBlockIndex) {
            return getPosition.apply(outerBlockIndex, innerBlockIndex);
        }

        Position getCellPosition(int innerCellIndex) {
            return getPosition.apply(outerCellIndex, innerCellIndex);
        }

        @Override
        public SudokoCell get(int index) {
            if (index < 0 || index >= 9)
                throw new IndexOutOfBoundsException();

            int innerBlockIndex = index / 3;
            int innerCellIndex = index % 3;
            return blocks.get(getBlockPosition(innerBlockIndex)).getCell(getCellPosition(innerCellIndex));
        }

        @Override
        public int size() {
            return 9;
        }
    }

    public List<SudokoCell> getRow(int row) {
        return new Cross(row, (outer, inner) -> new Position(outer, inner));
    }

    public List<SudokoCell> getColumn(int row) {
        return new Cross(row, (outer, inner) -> new Position(inner, outer));
    }

    public List<List<SudokoCell>> getRows() {
        return new AbstractList<List<SudokoCell>>() {
            @Override
            public List<SudokoCell> get(int index) {
                return getRow(index);
            }

            @Override
            public int size() {
                return 9;
            }
        };
    }

    public List<List<SudokoCell>> getColumns() {
        return new AbstractList<List<SudokoCell>>() {
            @Override
            public List<SudokoCell> get(int index) {
                return getColumn(index);
            }

            @Override
            public int size() {
                return 9;
            }
        };
    }

    private boolean solveUnits(Iterable<? extends Iterable<SudokoCell>> units) throws InconsistenceException {
        boolean progress = false;
        for (Iterable<SudokoCell> unit : units)
            progress |= solveUnit(unit);
        return progress;
    }

    private boolean solveUnit(Iterable<SudokoCell> unit) throws InconsistenceException {
        ListMultimap<Integer, SudokoCell> possiblities = Multimaps.newListMultimap(new HashMap<>(), ArrayList::new);

        for (SudokoCell cell : unit) {
            for (int possibleNumber : cell.getPossibleValues()) {
                possiblities.put(possibleNumber, cell);
            }
        }

        boolean progress = false;

        for (Entry<Integer, Collection<SudokoCell>> e : possiblities.asMap().entrySet()) {
            int possibleNumber = e.getKey();
            Collection<SudokoCell> possibleCells = e.getValue();

            if (possibleCells.size() == 1) {
                SudokoCell next = possibleCells.iterator().next();

                if (!next.isComplete()) {
                    next.setNumber(possibleNumber);
                    progress = true;
                }
            }
        }

        return progress;
    }

    public void reset() {
        for (CellBlock block : blocks)
            block.reset();

        //Wenn alles leer ist, sind mgliche Zahlen auch korrekt zurckgesetzt
        state = PossibleNumersCalculationState.CALCULATED;
    }

    public void solve() {
        try {
            //Neu berechnen, damit Animation vollstndig luft
            recalculate();

            int round = 0;

            while (true) {
                boolean progress = false;

                progress |= solveUnits(blocks);
                progress |= solveUnits(getRows());
                progress |= solveUnits(getColumns());

                System.out.println("Round " + ++round);

                if (!progress)
                    break;
            }

            System.out.println("Done.");
        } catch (InconsistenceException e) {
            onInconsistenceOccured(e);
        }
    }

    public PossibleNumersCalculationState getState() {
        return state;
    }

    void onInconsistenceOccured(InconsistenceException e) {
        setState(PossibleNumersCalculationState.INCONSISTENT);
        e.printStackTrace();
    }
}