Java tutorial
/* * 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(); } }