001package ball.game.sudoku;
002/*-
003 * ##########################################################################
004 * Game Applications and Utilities
005 * $Id: Puzzle.java 5285 2020-02-05 04:23:21Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-game/trunk/src/main/java/ball/game/sudoku/Puzzle.java $
007 * %%
008 * Copyright (C) 2010 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import ball.util.Coordinate;
024import ball.util.CoordinateMap;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.List;
028
029/**
030 * Sudoku {@link Puzzle}.
031 *
032 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
033 * @version $Revision: 5285 $
034 */
035public class Puzzle extends CoordinateMap<Cell> {
036    private static final long serialVersionUID = -4650945638478115982L;
037
038    /** @serial */ private final List<CoordinateMap<Cell>> nonets;
039
040    /**
041     * Sole constructor.
042     */
043    public Puzzle() {
044        super();
045
046        for (Coordinate key : Coordinate.range(0, 0, 9, 9)) {
047            put(key, new Cell());
048        }
049
050        ArrayList<CoordinateMap<Cell>> list = new ArrayList<>();
051
052        for (int y = 0, yN = getRowCount(); y < yN; y += 3) {
053            for (int x = 0, xN = getColumnCount(); x < xN; x += 3) {
054                list.add(subMap(y, x, y + 3, x + 3));
055            }
056        }
057
058        nonets = Collections.unmodifiableList(list);
059    }
060
061    /**
062     * Method to get the sub-{@link CoordinateMap}s representing the 3x3 boxes
063     * (nonets).
064     *
065     * @return  The {@link List} of 3x3 nonet {@link CoordinateMap}s.
066     */
067    public List<CoordinateMap<Cell>> nonets() {
068        return Collections.unmodifiableList(nonets);
069    }
070
071    /**
072     * Method to get the sub-{@link CoordinateMap}s representing the
073     * 9-{@link Cell} groups where the digits 1-9 must appear exactly once.
074     * See {@link #rows()}, {@link #columns()}, and {@link #nonets()}.
075     *
076     * @return  The {@link List} of Sudoku sub-{@link CoordinateMap}s.
077     */
078    public List<CoordinateMap<Cell>> subMaps() {
079        ArrayList<CoordinateMap<Cell>> list = new ArrayList<>();
080
081        list.addAll(rows());
082        list.addAll(columns());
083        list.addAll(nonets());
084
085        return list;
086    }
087
088    /**
089     * Method to get the sub-{@link CoordinateMap}s representing the
090     * 9-{@link Cell} groups including the argument {@link Cell}.  See
091     * {@link #rows()}, {@link #columns()}, and {@link #nonets()}.
092     *
093     * @see #subMaps()
094     *
095     * @param   cell            The argument {@link Cell}.
096     *
097     * @return  The {@link List} of Sudoku sub-{@link CoordinateMap}s for the
098     *          argument {@link Cell}.
099     */
100    public List<CoordinateMap<Cell>> subMapsOf(Cell cell) {
101        ArrayList<CoordinateMap<Cell>> list = new ArrayList<>();
102
103        for (CoordinateMap<Cell> map : subMaps()) {
104            if (cell.isIn(map.values())) {
105                list.add(map);
106            }
107        }
108
109        return list;
110    }
111
112    /**
113     * Method to test if the current {@link Cell} values constitute a legal
114     * Sudoku puzzle.
115     *
116     * @return  {@code true} if {@link.this} {@link Puzzle} is legal;
117     *          {@code false} otherwise.
118     */
119    public boolean isLegal() {
120        boolean legal = true;
121
122        for (CoordinateMap<Cell> grid : subMaps()) {
123            legal &= isLegal(grid);
124        }
125
126        return legal;
127    }
128
129    private boolean isLegal(CoordinateMap<Cell> map) {
130        boolean legal = true;
131        Digits digits = new Digits();
132
133        for (Cell cell : map.values()) {
134            if (cell.isSolved()) {
135                legal &= digits.addAll(cell);
136
137                if (! legal) {
138                    break;
139                }
140            }
141        }
142
143        return legal;
144    }
145
146    /**
147     * Method to test if the current {@link Cell} values constitute a solved
148     * Sudoku puzzle.
149     *
150     * @return  {@code true} if {@link.this} {@link Puzzle} is solved;
151     *          {@code false} otherwise.
152     */
153    public boolean isSolved() {
154        boolean solved = isLegal();
155
156        for (Cell cell : values()) {
157            solved &= cell.isSolved();
158
159            if (! solved) {
160                break;
161            }
162        }
163
164        return solved;
165    }
166
167    /**
168     * Method to apply the argument {@link Rule} to solve {@link.this}
169     * {@link Puzzle}.
170     *
171     * @param   rule            The {@link Rule} to apply.
172     *
173     * @return  {@code true} if {@link Puzzle} is modified;
174     *          {@code false} otherwise.
175     */
176    public boolean apply(Rule rule) { return rule.applyTo(this); }
177}