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}