2 * Sudokuki - essential sudoku game
3 * Copyright (C) 2007-2012 Sylvain Vedrenne
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.jankenpoi.sudokuki.model;
20 import java.util.ArrayList;
21 import java.util.List;
23 import net.jankenpoi.sudokuki.SudokuGrid;
24 import net.jankenpoi.sudokuki.generator.SudokuGeneratorFactory;
25 import net.jankenpoi.sudokuki.preferences.UserPreferences;
26 import net.jankenpoi.sudokuki.view.GridListener;
29 * (MVC) model for a sudoku grid
31 * @author Sylvain Vedrenne
34 public class GridModel implements Cloneable {
36 public static final int MASK_CELL_VALUES = 0x000F; // ___0000.0000-0000.1111
37 public static final int MASK_CELL_MEMOS = 0x1FF0; // ____0001.1111-1111.0000
39 public static final int FLAG_CELL_MEMO_1 = 0x0010; // ___0000.0000-0001.0000
40 public static final int FLAG_CELL_MEMO_2 = 0x0020; // ___0000.0000-0010.0000
41 public static final int FLAG_CELL_MEMO_3 = 0x0040; // ___0000.0000-0100.0000
42 public static final int FLAG_CELL_MEMO_4 = 0x0080; // ___0000.0000-1000.0000
43 public static final int FLAG_CELL_MEMO_5 = 0x0100; // ___0000.0001-0000.0000
44 public static final int FLAG_CELL_MEMO_6 = 0x0200; // ___0000.0010-0000.0000
45 public static final int FLAG_CELL_MEMO_7 = 0x0400; // ___0000.0100-0000.0000
46 public static final int FLAG_CELL_MEMO_8 = 0x0800; // ___0000.1000-0000.0000
47 public static final int FLAG_CELL_MEMO_9 = 0x1000; // ___0001.0000-0000.0000
48 public static final int FLAG_CELL_READ_ONLY = 0x2000; // 0010.0000-0000.0000
50 public static final int FLAG_GRID_COMPLETE = 0x4000; // _0100.0000-0000.0000
52 private List<GridListener> listeners = new ArrayList<GridListener>();
54 private boolean isCustomGridModeON = false;
58 * Values and flags for all cells
60 private short[] cellInfos = new short[81];
62 public GridModel(short[] flagsTable, int startIdx) {
63 copyFlagsToGrid(flagsTable, startIdx);
66 private void copyFlagsToGrid(short [] flagsTable, int startIdx) {
67 for (int i = 0; i < cellInfos.length; i++) {
68 short value = (short) (flagsTable[startIdx + i] & MASK_CELL_VALUES);
70 if (1 <= value && value <= 9) {
71 cellInfos[i] |= FLAG_CELL_READ_ONLY;
80 public void resetGridModelFromShorts(short[] externalCellInfos) {
81 cellInfos = new short[81];
83 for (int i=0; i<81; i++) {
84 cellInfos[i] = externalCellInfos[i];
88 private void newGrid() {
89 final int minRating = UserPreferences.getInstance().getInteger("minRating", 0);
90 final int maxRating = UserPreferences.getInstance().getInteger("maxRating", Integer.MAX_VALUE);
91 SudokuGrid grid = SudokuGeneratorFactory.getGenerator().generateGrid(minRating, maxRating);
93 for (int i = 0; i < cellInfos.length; i++) {
94 short value = (short) grid.getValueAt(i);
96 if (1 <= value && value <= 9) {
97 cellInfos[i] |= FLAG_CELL_READ_ONLY;
103 * FIXME: For the moment, this constructor is for testing use only....
107 public GridModel(String strValues) {
108 if (strValues == null) {
112 .println("GridModel.GridModel() length:" + strValues.length());
113 System.out.println("GridModel.GridModel() strValues:" + strValues);
114 for (int i = 0; i < strValues.length(); i++) {
115 short value = Short.valueOf(strValues.substring(i, i + 1));
116 System.out.print(value);
117 cellInfos[i] = value;
118 if (1 <= value && value <= 9) {
119 cellInfos[i] |= FLAG_CELL_READ_ONLY;
122 System.out.println();
125 public void addGridListener(GridListener view) {
129 short getCellInfosAt(int li, int co) {
130 return cellInfos[9 * li + co];
133 public void setMemosForAllCells() {
134 for (int li = 0; li < 9; li++) {
135 for (int co = 0; co < 9; co++) {
136 if (!isCellFilled(li, co)) {
137 cellInfos[9 * li + co] = MASK_CELL_MEMOS;
142 // parcourir tous les carres
143 // - pour chaque carre, cribler les memos
144 // -- pour chaque cellule, cribler les memos
146 for (int X = 0; X < 9; X += 3) { // left pos of a square
147 for (int Y = 0; Y < 9; Y += 3) { // top pos of a square
148 short currentValuesMask = 0;
149 for (int x = 0; x < 3; x++) {
150 for (int y = 0; y < 3; y++) {
153 if (isCellFilled(li, co)) {
154 byte value = getValueAt(li, co);
155 currentValuesMask |= getMemoFlag(value);
159 for (int x = 0; x < 3; x++) {
160 for (int y = 0; y < 3; y++) {
163 if (!isCellFilled(li, co)) {
164 cellInfos[9 * li + co] &= ~currentValuesMask;
171 // parcourir toutes les lignes
172 // - pour chaque ligne, cribler les memos
173 // -- pour chaque cellule, cribler les memos
175 for (int li = 0; li < 9; li++) { // each line
176 short currentValuesMask = 0;
177 for (int co = 0; co < 9; co++) {
178 if (isCellFilled(li, co)) {
179 byte value = getValueAt(li, co);
180 currentValuesMask |= getMemoFlag(value);
183 for (int co = 0; co < 9; co++) {
184 if (!isCellFilled(li, co)) {
186 // .println("GridModel.setMemosForAllCells() currentValuesMask:"
187 // + Integer.toHexString(currentValuesMask));
188 cellInfos[9 * li + co] &= ~currentValuesMask;
193 // parcourir toutes les colonnes
194 // - pour chaque colonne, cribler les memos
195 // -- pour chaque cellule, cribler les memos
196 for (int co = 0; co < 9; co++) { // each column
197 short currentValuesMask = 0;
198 for (int li = 0; li < 9; li++) {
199 if (isCellFilled(li, co)) {
200 byte value = getValueAt(li, co);
201 currentValuesMask |= getMemoFlag(value);
204 for (int li = 0; li < 9; li++) {
205 if (!isCellFilled(li, co)) {
207 .println("GridModel.setMemosForAllCells() currentValuesMask:"
208 + Integer.toHexString(currentValuesMask));
209 cellInfos[9 * li + co] &= ~currentValuesMask;
213 fireGridChanged(new GridChangedEvent(this, 0, 0, (short)0));
216 public void setMemosForThisCell(int cellLi, int cellCo) {
217 if (isCellFilled(cellLi, cellCo))
219 cellInfos[9 * cellLi + cellCo] = MASK_CELL_MEMOS;
222 // parcourir le carre courant
223 // - pour chaque carre, cribler les memos
224 // -- pour chaque cellule, cribler les memos
226 { // left pos of a square
228 int X = 3 * (cellCo / 3);
229 int Y = 3 * (cellLi / 3);
230 System.out.println("GridModel.setMemosForThisCell() cellLi:"+cellLi+" cellCo:"+cellCo+" X:"+X+" Y:"+Y);
231 short currentValuesMask = 0;
232 for (int x = 0; x < 3; x++) {
233 for (int y = 0; y < 3; y++) {
236 if (isCellFilled(li, co)) {
237 byte value = getValueAt(li, co);
238 currentValuesMask |= getMemoFlag(value);
242 cellInfos[9 * cellLi + cellCo] &= ~currentValuesMask;
245 // parcourir la ligne de cette cellule
246 // - pour chaque ligne, cribler les memos
247 // -- pour chaque cellule, cribler les memos
250 short currentValuesMask = 0;
251 for (int co = 0; co < 9; co++) {
252 if (isCellFilled(cellLi, co)) {
253 byte value = getValueAt(cellLi, co);
254 currentValuesMask |= getMemoFlag(value);
257 for (int co = 0; co < 9; co++) {
258 if (!isCellFilled(cellLi, co)) {
260 // .println("GridModel.setMemosForAllCells() currentValuesMask:"
261 // + Integer.toHexString(currentValuesMask));
262 cellInfos[9 * cellLi + co] &= ~currentValuesMask;
267 // parcourir la colonne de cette cellule
268 // - pour chaque colonne, cribler les memos
269 // -- pour chaque cellule, cribler les memos
271 short currentValuesMask = 0;
272 for (int li = 0; li < 9; li++) {
273 if (isCellFilled(li, cellCo)) {
274 byte value = getValueAt(li, cellCo);
275 currentValuesMask |= getMemoFlag(value);
278 for (int li = 0; li < 9; li++) {
279 if (!isCellFilled(li, cellCo)) {
281 .println("GridModel.setMemosForAllCells() currentValuesMask:"
282 + Integer.toHexString(currentValuesMask));
283 cellInfos[9 * li + cellCo] &= ~currentValuesMask;
287 fireGridChanged(new GridChangedEvent(this, cellLi, cellCo, cellInfos[9
288 * cellLi + cellCo]));
291 public void setCellMemos(int li, int co, byte[] values) {
292 for (int i = 0; i < values.length; i++) {
293 setCellMemo(li, co, values[i]);
295 fireGridChanged(new GridChangedEvent(this, li, co, cellInfos[9 * li
299 private short getMemoFlag(byte value) {
302 return FLAG_CELL_MEMO_1;
304 return FLAG_CELL_MEMO_2;
306 return FLAG_CELL_MEMO_3;
308 return FLAG_CELL_MEMO_4;
310 return FLAG_CELL_MEMO_5;
312 return FLAG_CELL_MEMO_6;
314 return FLAG_CELL_MEMO_7;
316 return FLAG_CELL_MEMO_8;
318 return FLAG_CELL_MEMO_9;
320 throw new IllegalArgumentException("GridModel.getMemoFlag() value "
321 + value + " is illegal");
325 public byte getNbOfPossibleValues(int li, int co) {
326 short infos = cellInfos[9 * li + co];
328 for (byte i = 1; i <= 9; i++) {
329 nb += ((infos & getMemoFlag(i)) != 0) ? 1 : 0;
334 private void setCellMemo(int li, int co, byte value) {
335 cellInfos[9 * li + co] |= getMemoFlag(value);
338 public void clearCellMemos(int li, int co) {
339 cellInfos[9 * li + co] &= ~MASK_CELL_MEMOS;
342 public boolean isCellValueSet(int li, int co, Byte value) {
343 return (cellInfos[9 * li + co] & value.byteValue()) != 0;
346 public boolean isCellFilled(int li, int co) {
347 return (cellInfos[9 * li + co] & MASK_CELL_VALUES) != 0;
351 * For use for initial values in the grid, and when doing a custom grid, for
357 public void setCellReadOnly(int li, int co) {
358 cellInfos[9 * li + co] |= FLAG_CELL_READ_ONLY;
361 public void setCellValue(int li, int co, int value, boolean silent) {
362 cellInfos[9 * li + co] &= ~MASK_CELL_VALUES;
363 cellInfos[9 * li + co] |= value;
364 clearCellMemos(li, co);
366 if (silent == false) {
367 fireGridChanged(new GridChangedEvent(this, li, co, cellInfos[9 * li
371 if (!silent && isGridFull()) {
372 GridValidity validity = checkGridValidity();
373 if (validity.isGridValid()) {
376 fireGridChanged(new GridChangedEvent(this, 0, 0, (short)0));
380 public void fireGridChanged(GridChangedEvent event) {
382 for (GridListener listener : listeners) {
383 listener.gridChanged(event);
387 public boolean isCellMemoSet(int li, int co, byte value) {
388 return (cellInfos[9 * li + co] & getMemoFlag(value)) != 0;
391 public boolean isCellReadOnly(int li, int co) {
392 return !isCustomGridModeON && (cellInfos[9 * li + co] & FLAG_CELL_READ_ONLY) != 0;
395 public boolean isGridFull() {
396 for (int li=0; li<9; li++) {
397 for (int co=0; co<9; co++) {
398 if (!isCellFilled(li, co)) {
406 public boolean isGridComplete() {
407 return (cellInfos[0] & FLAG_GRID_COMPLETE) != 0;
410 public void setGridResolved() {
411 cellInfos[0] |= FLAG_GRID_COMPLETE;
415 private void fireGridResolved() {
416 for (GridListener listener : listeners) {
417 listener.gridResolved();
421 public void setGridComplete() {
422 cellInfos[0] |= FLAG_GRID_COMPLETE;
426 private void fireGridComplete() {
427 for (GridListener listener : listeners) {
428 listener.gridComplete();
432 public byte getValueAt(int li, int co) {
433 byte value = (byte) (cellInfos[9 * li + co] & MASK_CELL_VALUES);
437 public int[] cloneCellInfosAsInts() {
438 int[] ints = new int[cellInfos.length];
439 for (int i=0; i<cellInfos.length; i++) {
440 ints[i] = cellInfos[i];
441 // System.out.println("GridModel.cloneCellInfosAsInts() cellInfos["+i+"]:"+cellInfos[i]);
442 // System.out.println("GridModel.cloneCellInfosAsInts() ints["+i+"]:"+ints[i]);
447 public void requestNewGrid() {
451 public void clearAllUserMoves() {
452 for (int li = 0; li < 9; li++) {
453 for (int co = 0; co < 9; co++) {
454 if (!isCellReadOnly(li, co)) {
455 cellInfos[9 * li + co] &= ~MASK_CELL_VALUES;
459 fireGridChanged(new GridChangedEvent(this, 0, 0, (short)0));
462 public void clearAllUserMemos() {
463 for (int li = 0; li < 9; li++) {
464 for (int co = 0; co < 9; co++) {
465 clearCellMemos(li, co);
468 fireGridChanged(new GridChangedEvent(this, 0, 0, (short)0));
471 public void enterCustomGridMode() {
472 isCustomGridModeON = true;
473 fireGridChanged(new GridChangedEvent(this, 0, 0, (short)0));
476 public GridValidity getGridValidity() {
477 if (!isCustomGridModeON && !isGridFull()) {
478 return GridValidity.VALID;
480 return checkGridValidity();
483 public void exitCustomGridMode() {
484 isCustomGridModeON = false;
485 for (int li = 0; li < 9; li++) {
486 for (int co = 0; co < 9; co++) {
487 if (isCellFilled(li, co)) {
488 setCellReadOnly(li, co);
490 cellInfos[9 * li + co] &= ~FLAG_CELL_READ_ONLY;
494 fireGridChanged(new GridChangedEvent(this, 0, 0, (short)0));
497 public boolean getCustomGridMode() {
498 return isCustomGridModeON;
501 public static class GridValidity {
502 public static final GridValidity VALID = new GridValidity(true, null, null, null, null);
504 private boolean isValid = true;
505 private Integer firstErrorLine = null;
506 private Integer firstErrorColumn = null;
507 private Integer firstErrorSquareX = null;
508 private Integer firstErrorSquareY = null;
510 private GridValidity(boolean isValid, Integer lineWithError, Integer columnWithError,
511 Integer squareWithErrorX, Integer squareWithErrorY) {
512 this.isValid = isValid;
513 this.firstErrorLine = lineWithError;
514 this.firstErrorColumn = columnWithError;
515 this.firstErrorSquareX = squareWithErrorX;
516 this.firstErrorSquareY = squareWithErrorY;
519 private static GridValidity valueOf(boolean isValid, Integer lineWithError, Integer columnWithError,
520 Integer squareWithErrorX, Integer squareWithErrorY) {
524 return new GridValidity(isValid, lineWithError, columnWithError,
525 squareWithErrorX, squareWithErrorY);
530 public boolean isGridValid() {
534 public Integer getFirstErrorLine() {
535 return firstErrorLine;
538 public Integer getFirstErrorColumn() {
539 return firstErrorColumn;
542 public Integer getFirstErrorSquareX() {
543 return firstErrorSquareX;
546 public Integer getFirstErrorSquareY() {
547 return firstErrorSquareY;
552 private GridValidity checkGridValidity() {
553 boolean isValid = true;
554 Integer lineWithError = null;
555 Integer columnWithError = null;
556 Integer squareWithErrorX = null;
557 Integer squareWithErrorY = null;
559 // Check validity of all lines
560 for (int li = 0; isValid && li < 9; li++) {
561 byte[] numbers = new byte[10];
562 for (int co = 0; co < 9; co++) {
563 byte value = getValueAt(li, co);
564 if (numbers[value] != 0) {
566 lineWithError = Integer.valueOf(li);
569 numbers[value] = value;
572 // Check validity of all columns
573 for (int co = 0; isValid && co < 9; co++) {
574 byte[] numbers = new byte[10];
575 for (int li = 0; li < 9; li++) {
576 byte value = getValueAt(li, co);
577 if (numbers[value] != 0) {
579 columnWithError = Integer.valueOf(co);
582 numbers[value] = value;
585 // Check validity of all squares
586 for (int X = 0; isValid && X < 9; X += 3) { // left pos of a square
587 for (int Y = 0; isValid && Y < 9; Y += 3) { // top pos of a square
588 int[] numbers = new int[10];
589 for (int x = 0; isValid && x < 3; x++) {
590 for (int y = 0; isValid && y < 3; y++) {
593 byte value = getValueAt(li, co);
594 if (numbers[value] != 0) {
596 squareWithErrorX = Integer.valueOf(X);
597 squareWithErrorY = Integer.valueOf(Y);
600 numbers[value] = value;
605 return GridValidity.valueOf(isValid, lineWithError, columnWithError,
606 squareWithErrorX, squareWithErrorY);
609 public boolean areSomeMemosSet() {
610 for (int li = 0; li < 9; li++) {
611 for (int co = 0; co < 9; co++) {
612 if ((cellInfos[9 * li + co] & MASK_CELL_MEMOS) != 0)
619 public boolean areSomeCellsFilled() {
620 for (int li = 0; li < 9; li++) {
621 for (int co = 0; co < 9; co++) {
622 if (isCellReadOnly(li, co))
624 if ((cellInfos[9 * li + co] & MASK_CELL_VALUES) != 0)
631 public boolean areSomeCellsEmpty() {
632 for (int li = 0; li < 9; li++) {
633 for (int co = 0; co < 9; co++) {
634 if (isCellFilled(li, co)) {
644 public int[] asIntArray() {
645 int[] ints = new int[cellInfos.length];
646 for (int i=0; i<cellInfos.length; i++) {
647 ints[i] = cellInfos[i];