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}