1/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2/* 3 * Copyright © 2014 Parin Porecha 4 * Copyright © 2014 Michael Catanzaro 5 * 6 * This file is part of GNOME Sudoku. 7 * 8 * GNOME Sudoku is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * GNOME Sudoku is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with GNOME Sudoku. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 22using Gee; 23 24public class SudokuBoard : Object 25{ 26 /* Implemented in such a way that it can be extended for other sizes ( like 2x3 sudoku or 4x4 sudoku ) instead of normal 3x3 sudoku. */ 27 28 protected int[,] cells; /* stores the value of the cells */ 29 public bool[,] is_fixed; /* if the value at location is fixed or not */ 30 private bool[,] possible_in_row; /* if specific value is possible in specific row */ 31 private bool[,] possible_in_col; /* if specific value is possible in specific col */ 32 private bool[,,] possible_in_block; /* if specific value is possible in specific block */ 33 34 private bool[,,] earmarks; /* Earmarks set by the user */ 35 private int n_earmarks; /* The number of earmarks on the board */ 36 37 public double previous_played_time { set; get; default = 0; } 38 39 public DifficultyCategory difficulty_category { set; get; default = DifficultyCategory.UNKNOWN; } 40 41 /* Number of rows in one block */ 42 public int block_rows { get; private set; } 43 44 /* Number of columns in one block */ 45 public int block_cols { get; private set; } 46 47 /* Number of rows in board */ 48 public int rows { get; private set; } 49 50 /* Number of columns in board */ 51 public int cols { get; private set; } 52 53 /* Maximum possible val on board. 9 for 3x3 sudoku*/ 54 public int max_val 55 { 56 get { return block_rows * block_cols; } 57 } 58 59 public bool broken 60 { 61 get { return broken_coords.size != 0; } 62 } 63 64 /* the number of filled squares on the board */ 65 public int filled { get; private set; } 66 67 /* the number of fixed squares on the board */ 68 public int fixed { get; private set; } 69 70 public int size 71 { 72 get { return rows * cols; } 73 } 74 75 public bool complete 76 { 77 get { return filled == cols * rows && !broken; } 78 } 79 80 public bool is_empty () 81 { 82 return filled == fixed && n_earmarks == 0; 83 } 84 85 public bool is_fully_filled () 86 { 87 return filled == cols * rows; 88 } 89 90 public signal void completed (); 91 92 /* The set of coordinates on the board which are invalid */ 93 public Gee.Set<Coord?> broken_coords; 94 95 /* The list of coordinates for each column on the board */ 96 public Gee.List<Gee.List<Coord?>> coords_for_col; 97 98 /* The list of coordinates for each row on the board */ 99 public Gee.List<Gee.List<Coord?>> coords_for_row; 100 101 /* The map from the coordinate of a box, to the list of coordinates in that box, for each box on the board */ 102 public Map<Coord?, Gee.List<Coord?>> coords_for_block; 103 104 public SudokuBoard (int block_rows = 3, int block_cols = 3) 105 { 106 rows = cols = block_rows * block_cols; 107 this.block_rows = block_rows; 108 this.block_cols = block_cols; 109 cells = new int[rows, cols]; 110 is_fixed = new bool[rows, cols]; 111 possible_in_row = new bool[rows, cols]; 112 possible_in_col = new bool[cols, rows]; 113 possible_in_block = new bool[block_rows, block_cols, block_rows * block_cols]; 114 earmarks = new bool[rows, cols, max_val]; 115 116 for (var l1 = 0; l1 < rows; l1++) 117 { 118 for (var l2 = 0; l2 < cols; l2++) 119 { 120 cells[l1, l2] = 0; 121 is_fixed[l1, l2] = false; 122 possible_in_row[l1, l2] = true; 123 possible_in_col[l2, l1] = true; 124 } 125 } 126 for (var l1 = 0; l1 < block_rows; l1++) 127 for (var l2 = 0; l2 < block_cols; l2++) 128 for (var l3 = 0; l3 < max_val; l3++) 129 possible_in_block[l1, l2, l3] = true; 130 131 broken_coords = new HashSet<Coord?>((HashDataFunc<Coord>) Coord.hash, (EqualDataFunc<Coord>) Coord.equal); 132 133 coords_for_col = new ArrayList<ArrayList<Coord?>> (); 134 for (int col = 0; col < cols; col++) 135 { 136 coords_for_col.add (new ArrayList<Coord?> ((EqualDataFunc<Coord>) Coord.equal)); 137 for (int row = 0; row < rows; row++) 138 coords_for_col.get (col).add (Coord(row, col)); 139 140 coords_for_col[col] = coords_for_col[col].read_only_view; 141 } 142 coords_for_col = coords_for_col.read_only_view; 143 144 coords_for_row = new ArrayList<ArrayList<Coord?>> (); 145 for (int row = 0; row < rows; row++) 146 { 147 coords_for_row.add (new ArrayList<Coord?> ((EqualDataFunc<Coord>) Coord.equal)); 148 for (int col = 0; col < cols; col++) 149 coords_for_row.get (row).add (Coord(row, col)); 150 151 coords_for_row[row] = coords_for_row[row].read_only_view; 152 } 153 coords_for_row = coords_for_row.read_only_view; 154 155 coords_for_block = new HashMap<Coord?, Gee.List<Coord?>> ((HashDataFunc<Coord>) Coord.hash, (EqualDataFunc<Coord>) Coord.equal); 156 for (int col = 0; col < block_cols; col++) 157 for (int row = 0; row < block_rows; row++) 158 coords_for_block.set (Coord(row, col), new ArrayList<Coord?> ((EqualDataFunc<Coord>) Coord.equal)); 159 160 for (int col = 0; col < cols; col++) 161 for (int row = 0; row < rows; row++) 162 coords_for_block.get(Coord(row / block_rows, col / block_cols)).add(Coord(row, col)); 163 164 for (int col = 0; col < block_cols; col++) 165 for (int row = 0; row < block_rows; row++) 166 coords_for_block[Coord(row, col)] = coords_for_block[Coord(row, col)].read_only_view; 167 168 coords_for_block = coords_for_block.read_only_view; 169 } 170 171 public SudokuBoard clone () 172 { 173 SudokuBoard board = new SudokuBoard (block_rows , block_cols); 174 board.cells = cells; 175 board.is_fixed = is_fixed; 176 board.possible_in_row = possible_in_row; 177 board.possible_in_col = possible_in_col; 178 board.possible_in_block = possible_in_block; 179 board.filled = filled; 180 board.fixed = fixed; 181 board.n_earmarks = n_earmarks; 182 board.broken_coords.add_all (broken_coords); 183 board.earmarks = earmarks; 184 board.difficulty_category = difficulty_category; 185 186 return board; 187 } 188 189 public void enable_earmark (int row, int column, int digit) 190 ensures (n_earmarks > 0) 191 { 192 if (!earmarks[row, column, digit-1]) 193 { 194 earmarks[row, column, digit-1] = true; 195 n_earmarks++; 196 } 197 } 198 199 public void disable_earmark (int row, int column, int digit) 200 ensures (n_earmarks >= 0) 201 { 202 if (earmarks[row, column, digit-1]) 203 { 204 earmarks[row, column, digit-1] = false; 205 n_earmarks--; 206 } 207 } 208 209 public void disable_all_earmarks (int row, int column) 210 ensures (n_earmarks >= 0) 211 { 212 for (var i = 1; i <= max_val; i++) 213 if (earmarks[row, column, i-1]) 214 disable_earmark (row, column, i); 215 } 216 217 public bool is_earmark_enabled (int row, int column, int digit) 218 { 219 return earmarks[row, column, digit-1]; 220 } 221 222 public bool is_possible (int row, int col, int val) 223 { 224 val--; 225 return (possible_in_row[row, val] && possible_in_col[col, val] && possible_in_block [row / block_cols, col / block_rows, val]); 226 } 227 228 public int count_possibilities (int row, int col) 229 { 230 return get_possibilities(row, col).length; 231 } 232 233 public int[] get_possibilities (int row, int col) 234 { 235 if (cells [row, col] != 0) 236 return new int[0]; 237 238 var possibilities = new int[9]; 239 var count = 0; 240 241 for (var l = 1; l <= max_val; l++) 242 { 243 if (is_possible (row, col, l)) 244 { 245 possibilities[count] = l; 246 count++; 247 } 248 } 249 return possibilities[0:count]; 250 } 251 252 public bool[] get_possibilities_as_bool_array (int row, int col) 253 { 254 var possibilities = new bool[max_val]; 255 256 for (var l = 1; l <= max_val; l++) 257 possibilities[l - 1] = is_possible (row, col, l); 258 259 return possibilities; 260 } 261 262 public Coord get_block_for(int row, int col) 263 { 264 return Coord(row / block_rows, col / block_cols); 265 } 266 267 public void insert (int row, int col, int val, bool is_fixed = false) 268 { 269 /* This should not happen when coded properly ;) */ 270 assert (val > 0); 271 assert (val <= max_val); 272 273 /* Cant insert in to a fixed cell, unless you know what you are doing */ 274 if (!is_fixed) 275 assert (!this.is_fixed[row, col]); 276 277 // If the cell has a previous value, remove it before continuing 278 if (cells[row, col] != 0) 279 remove(row, col, is_fixed); 280 281 cells[row, col] = val; 282 this.is_fixed[row, col] = is_fixed; 283 filled++; 284 if (is_fixed) 285 fixed++; 286 287 if (!possible_in_row[row, val - 1]) // If val was not possible in this row 288 mark_breakages_for(coords_for_row[row], val); // Mark the breakages 289 290 if (!possible_in_col[col, val - 1]) // If val was not possible in this col 291 mark_breakages_for(coords_for_col[col], val); // Mark the breakages 292 293 if (!possible_in_block[row / block_cols, col / block_rows, val - 1]) // If val was not possible in this block 294 mark_breakages_for(coords_for_block[Coord(row / block_cols, col / block_rows)], val); // Mark the breakages 295 296 // Then just mark it as not possible 297 val--; 298 possible_in_row[row, val] = false; 299 possible_in_col[col, val] = false; 300 possible_in_block[row / block_cols, col / block_rows, val] = false; 301 302 if (complete) 303 completed(); 304 } 305 306 public new void set (int row, int col, int val) 307 { 308 if (val == 0) 309 remove (row, col); 310 else if (val > 0 && val <= max_val) 311 insert (row, col, val); 312 else 313 assert_not_reached(); 314 } 315 316 public new int get (int row, int col) 317 { 318 return cells[row, col]; 319 } 320 321 public void remove (int row, int col, bool is_fixed = false) 322 { 323 /* You can't remove an empty cell */ 324 if (cells[row, col] == 0) 325 return; 326 327 /* You can't remove an fixed cell */ 328 if (!is_fixed) 329 assert (!this.is_fixed[row, col]); 330 331 int previous_val = cells[row, col]; 332 cells[row, col] = 0; 333 334 if (broken_coords.contains(Coord(row, col))) // If this cell was broken 335 { 336 // Remove all the related breakages in the related sets of cells 337 remove_breakages_for(coords_for_row[row], previous_val); 338 remove_breakages_for(coords_for_col[col], previous_val); 339 remove_breakages_for(coords_for_block[Coord(row / block_rows, col / block_cols)], previous_val); 340 broken_coords.remove(Coord(row, col)); 341 342 // Re-mark all the breakages, 343 mark_breakages_for(coords_for_row[row], previous_val); 344 mark_breakages_for(coords_for_col[col], previous_val); 345 mark_breakages_for(coords_for_block[Coord(row / block_rows, col / block_cols)], previous_val); 346 347 // and update the possibilities accordingly 348 possible_in_row[row, previous_val - 1] = get_occurances(coords_for_row[row], previous_val).size == 0; 349 possible_in_col[col, previous_val - 1] = get_occurances(coords_for_col[col], previous_val).size == 0; 350 possible_in_block[row / block_cols, col / block_rows, previous_val - 1] = get_occurances(coords_for_block[Coord(row / block_rows, col / block_cols)], previous_val).size == 0; 351 } 352 else // Not previously broken, so just mark as a possible value 353 { 354 previous_val--; 355 356 possible_in_row[row, previous_val] = true; 357 possible_in_col[col, previous_val] = true; 358 possible_in_block[row / block_cols, col / block_rows, previous_val] = true; 359 } 360 361 filled--; 362 363 if (is_fixed) 364 fixed--; 365 } 366 367 public int count_solutions_limited () 368 { 369 return QQwing.count_solutions_limited ((int[]) cells); 370 } 371 372 public Set<Coord?> get_occurances(Gee.List<Coord?> coords, int val) 373 { 374 Set<Coord?> occurances = new HashSet<Coord?>((HashDataFunc<Coord>) Coord.hash, (EqualDataFunc<Coord>) Coord.equal); 375 foreach (Coord coord in coords) 376 if (cells[coord.row, coord.col] == val) 377 occurances.add (coord); 378 379 return occurances; 380 } 381 382 public bool row_contains(int row, int val) 383 { 384 return get_occurances(coords_for_row[row], val).size != 0; 385 } 386 387 public bool col_contains(int col, int val) 388 { 389 return get_occurances(coords_for_col[col], val).size != 0; 390 } 391 392 public bool block_contains(Coord block, int val) 393 { 394 return get_occurances(coords_for_block[block], val).size != 0; 395 } 396 397 private void remove_breakages_for(Gee.List<Coord?> coords, int val) 398 { 399 foreach (Coord coord in coords) 400 if (cells[coord.row, coord.col] == val && broken_coords.contains(coord)) 401 broken_coords.remove(coord); 402 } 403 404 /* returns if val is possible in coords */ 405 private void mark_breakages_for(Gee.List<Coord?> coords, int val) 406 { 407 Set<Coord?> occurances = get_occurances(coords, val); 408 if (occurances.size != 1) 409 broken_coords.add_all(occurances); 410 } 411 412 public void to_initial_state () 413 { 414 for (var l1 = 0; l1 < rows; l1++) 415 for (var l2 = 0; l2 < cols; l2++) 416 if (!is_fixed[l1, l2]) 417 remove (l1, l2); 418 } 419 420 public void print (int indent = 0) 421 { 422 for (var l1 = 0; l1 < 9; l1++) 423 { 424 for (int i = 0; i < indent; i++) 425 stdout.printf(" "); 426 for (var l2 = 0; l2 < 9; l2++) 427 { 428 if (cells[l1,l2] != 0) 429 stdout.printf ("%d ", cells[l1,l2]); 430 else 431 stdout.printf (" "); 432 } 433 stdout.printf ("\n"); 434 } 435 stdout.flush (); 436 } 437 438 public void get_string () 439 { 440 stdout.printf ("[ "); 441 for (var l1 = 0; l1 < 9; l1++) 442 { 443 stdout.printf ("[ "); 444 for (var l2 = 0; l2 < 9; l2++) 445 { 446 stdout.printf ("%d", cells[l1,l2]); 447 if (l2 != 8) 448 stdout.printf (","); 449 } 450 stdout.printf (" ]"); 451 if (l1 != 8) 452 stdout.printf (","); 453 } 454 stdout.printf (" ]"); 455 } 456 457 public string to_string (bool get_original_state = false) 458 { 459 var board_string = ""; 460 for (var i = 0; i < rows; i++) 461 { 462 for (var j = 0; j < cols; j++) 463 { 464 if (is_fixed[i, j]) 465 board_string += cells[i, j].to_string (); 466 else 467 board_string += get_original_state ? "0" : cells[i, j].to_string (); 468 } 469 } 470 return board_string; 471 } 472 473 public int[,] get_cells() 474 { 475 return cells; 476 } 477 478 public HashMap<Coord?, Gee.List<int>> calculate_open_squares () 479 { 480 var possibilities = new HashMap<Coord?, Gee.List<int>> ((HashDataFunc<Coord>) Coord.hash, (EqualDataFunc<Coord>) Coord.equal); 481 for (var l1 = 0; l1 < rows; l1++) 482 { 483 for (var l2 = 0; l2 < cols; l2++) 484 { 485 if (cells[l1, l2] == 0) 486 { 487 Gee.List<int> possArrayList = new ArrayList<int> (); 488 int[] possArray = get_possibilities (l1, l2); 489 foreach (int i in possArray) 490 possArrayList.add (i); 491 possibilities[Coord(l1, l2)] = possArrayList; 492 } 493 } 494 } 495 return possibilities; 496 } 497 498 public bool is_finished () 499 { 500 var board_string = this.to_string (true) + ".save"; 501 var finishgame_file = Path.build_path (Path.DIR_SEPARATOR_S, SudokuSaver.finishgame_dir, board_string); 502 var file = File.new_for_path (finishgame_file); 503 504 return file.query_exists (); 505 } 506 507 public bool[] get_earmarks (int row, int col) 508 { 509 bool[] the_earmarks = new bool[max_val]; 510 for (var i = 1; i <= max_val; i++) 511 the_earmarks[i-1] = earmarks[row, col, i-1]; 512 return the_earmarks; 513 } 514 515 public string get_earmarks_string (int row, int col) 516 { 517 string s = ""; 518 for (var i = 1; i <= max_val; i++) 519 if (earmarks[row, col, i-1]) 520 s += i.to_string (); 521 522 return s; 523 } 524} 525 526public enum House 527{ 528 ROW, 529 COLUMN, 530 BLOCK 531} 532 533public struct Coord 534{ 535 public int row; 536 public int col; 537 538 public Coord(int row, int col) 539 { 540 this.row = row; 541 this.col = col; 542 } 543 544 public static int hash (Coord coord) 545 { 546 return (coord.row * 33) ^ coord.col; 547 } 548 549 public static bool equal (Coord a, Coord b) 550 { 551 return ((a.row == b.row) && (a.col == b.col)); 552 } 553} 554 555public struct Cell 556{ 557 public Coord coord; 558 public int val; 559 560 public Cell(Coord coord, int val) 561 { 562 this.coord = coord; 563 this.val = val; 564 } 565 566 public static int hash (Cell cell) 567 { 568 return (Coord.hash(cell.coord) * 33) ^ cell.val; 569 } 570 571 public static bool equal (Cell a, Cell b) 572 { 573 return (Coord.equal(a.coord, b.coord) && (a.val == b.val)); 574 } 575} 576 577public enum DifficultyCategory 578{ 579 UNKNOWN, 580 EASY, 581 MEDIUM, 582 HARD, 583 VERY_HARD, 584 CUSTOM; 585 586 public string to_string () 587 { 588 switch (this) 589 { 590 case UNKNOWN: 591 return _("Unknown Difficulty"); 592 case EASY: 593 return _("Easy Difficulty"); 594 case MEDIUM: 595 return _("Medium Difficulty"); 596 case HARD: 597 return _("Hard Difficulty"); 598 case VERY_HARD: 599 return _("Very Hard Difficulty"); 600 case CUSTOM: 601 return _("Custom Puzzle"); 602 default: 603 assert_not_reached (); 604 } 605 } 606 607 public string to_untranslated_string () 608 { 609 switch (this) 610 { 611 case UNKNOWN: 612 return "Unknown Difficulty"; 613 case EASY: 614 return "Easy Difficulty"; 615 case MEDIUM: 616 return "Medium Difficulty"; 617 case HARD: 618 return "Hard Difficulty"; 619 case VERY_HARD: 620 return "Very Hard Difficulty"; 621 case CUSTOM: 622 return "Custom Puzzle"; 623 default: 624 assert_not_reached (); 625 } 626 } 627 628 public static DifficultyCategory from_string (string input) 629 { 630 switch (input) 631 { 632 case "Unknown Difficulty": 633 return UNKNOWN; 634 case "Easy Difficulty": 635 return EASY; 636 case "Medium Difficulty": 637 return MEDIUM; 638 case "Hard Difficulty": 639 return HARD; 640 case "Very Hard Difficulty": 641 return VERY_HARD; 642 case "Custom Puzzle": 643 return CUSTOM; 644 default: 645 warning ("Could not parse difficulty level. Falling back to Easy difficulty"); 646 return EASY; 647 } 648 } 649} 650