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