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