1
/* ipuz-guesses.c
2
 *
3
 * Copyright 2022 Jonathan Blandford <jrb@gnome.org>
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library 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 GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
 *
19
 * SPDX-License-Identifier: (LGPL-2.1-or-later OR MIT)
20
 */
21

            
22
#include "ipuz-guesses.h"
23
#include "ipuz-cell.h"
24
#include "ipuz-magic.h"
25
#include "libipuz.h"
26

            
27
struct _IPuzGuesses
28
{
29
  grefcount ref_count;
30

            
31
  GArray *cells;
32
  guint rows;
33
  guint columns;
34

            
35
  gchar *puzzle_id;
36
};
37

            
38
typedef struct _IPuzGuessCell
39
{
40
  IPuzCellCellType cell_type;
41
  gchar *guess;
42
} IPuzGuessCell;
43

            
44

            
45
6
G_DEFINE_BOXED_TYPE (IPuzGuesses, ipuz_guesses, ipuz_guesses_ref, ipuz_guesses_unref)
46

            
47

            
48
static void
49
6
array_row_clear_func (GArray **cell_row)
50
{
51
6
  g_array_free (*cell_row, TRUE);
52
6
  *cell_row = NULL;
53
6
}
54

            
55
static void
56
18
array_cell_clear_func (IPuzGuessCell *cell)
57
{
58
18
  g_clear_pointer (&cell->guess, g_free);
59
18
}
60

            
61
static GArray *
62
6
new_row_array (void)
63
{
64
  GArray *row_array;
65

            
66
6
  row_array = g_array_new (FALSE, TRUE, sizeof (IPuzGuessCell));
67
6
  g_array_set_clear_func (row_array, (GDestroyNotify) array_cell_clear_func);
68

            
69
6
  return row_array;
70
}
71

            
72
static IPuzGuesses *
73
2
ipuz_guesses_new (void)
74
{
75
  IPuzGuesses *guesses;
76

            
77
2
  guesses = (IPuzGuesses *) g_new0 (IPuzGuesses, 1);
78
2
  g_ref_count_init (&guesses->ref_count);
79

            
80
2
  guesses->cells = g_array_new (FALSE, TRUE, sizeof (GArray*));
81
2
  g_array_set_clear_func (guesses->cells, (GDestroyNotify) array_row_clear_func);
82

            
83
2
  return guesses;
84
}
85

            
86
/**
87
 * ipuz_guesses_new_from_board:
88
 * @board: A IPuzBoard to use as the model
89
 * @copy_guesses: Whether to prepopulate the guesses from @board
90
 *
91
 * Creates a new IPuzGuesses object modeled after @board. If
92
 * @copy_guesses is %TRUE then any initial saved guesses are copied
93
 * into the new `IPuzGuesses`
94
 *
95
 * Returns (transfer full): a `IPuzGuesses`
96
 **/
97
IPuzGuesses *
98
1
ipuz_guesses_new_from_board (IPuzBoard *board,
99
                             gboolean   copy_guesses)
100
{
101
  IPuzGuesses *guesses;
102
  guint row, column;
103

            
104
1
  g_return_val_if_fail (IPUZ_IS_BOARD (board), NULL);
105

            
106
1
  guesses = ipuz_guesses_new ();
107

            
108
1
  guesses->rows = ipuz_board_get_height (board);
109
1
  guesses->columns = ipuz_board_get_width (board);
110

            
111
4
  for (row = 0; row < guesses->rows; row++)
112
    {
113
3
      GArray *new_row = new_row_array ();
114
3
      g_array_set_size (new_row, guesses->columns);
115

            
116
3
      g_array_append_val (guesses->cells, new_row);
117

            
118
12
      for (column = 0; column < guesses->columns; column++)
119
        {
120
9
          IPuzCellCoord coord = { .row = row, .column = column };
121
9
          IPuzGuessCell *cell = &(g_array_index (new_row, IPuzGuessCell, column));;
122
9
          IPuzCell *board_cell = ipuz_board_get_cell (board, coord);
123

            
124
9
          cell->cell_type = ipuz_cell_get_cell_type (board_cell);
125

            
126
9
          if (board_cell->cell_type == IPUZ_CELL_NORMAL)
127
            {
128
              /* It's not clear which takes priority when there's saved
129
               * guesses and initial val. Feels like a nonsensical corner
130
               * case. */
131
7
              if (board_cell->initial_val)
132
                {
133
                  cell->cell_type = IPUZ_CELL_NULL;
134
                }
135

            
136
7
              if (copy_guesses && board_cell->saved_guess)
137
                {
138
                  g_clear_pointer (&cell->guess, g_free);
139
                  cell->guess = g_strdup (board_cell->saved_guess);
140
                }
141
            }
142
        }
143
    }
144
1
  return guesses;
145
}
146

            
147
static void
148
3
ipuz_guesses_parse_row (GArray   *row,
149
                        JsonNode *node)
150
{
151
  JsonArray *array;
152
  guint len;
153

            
154
3
  if (!JSON_NODE_HOLDS_ARRAY (node))
155
    return;
156

            
157
3
  array = json_node_get_array (node);
158
3
  len = json_array_get_length (array);
159
3
  g_array_set_size (row, len);
160

            
161
12
  for (guint i = 0; i < len; i++)
162
    {
163
      JsonNode *element_node;
164
      IPuzGuessCell *cell;
165

            
166
9
      element_node = json_array_get_element (array, i);
167
9
      cell = &(g_array_index (row, IPuzGuessCell, i));
168
9
      if (JSON_NODE_HOLDS_NULL (element_node))
169
1
        cell->cell_type = IPUZ_CELL_NULL;
170
8
      else if (JSON_NODE_HOLDS_VALUE (element_node))
171
        {
172
          const gchar *guess;
173
8
          guess = json_node_get_string (element_node);
174

            
175
          /* At some point, we may need to support BLOCK characters
176
           * other than "#" */
177
8
          if (g_strcmp0 (guess, _IPUZ_DEFAULT_BLOCK) == 0)
178
1
            cell->cell_type = IPUZ_CELL_BLOCK;
179
          else
180
            {
181
7
              cell->cell_type = IPUZ_CELL_NORMAL;
182
7
              if (guess != NULL && strlen (guess) > 0)
183
3
                cell->guess = g_strdup (guess);
184
            }
185
        }
186
    }
187
}
188

            
189
static void
190
1
ipuz_guesses_parse_saved (IPuzGuesses *guesses,
191
                          JsonNode    *node)
192
{
193
  JsonArray *array;
194

            
195
1
  if (!JSON_NODE_HOLDS_ARRAY (node))
196
    return;
197

            
198
1
  array = json_node_get_array (node);
199
4
  for (guint i = 0; i < json_array_get_length (array); i++)
200
    {
201
      JsonNode *element_node;
202
      GArray *new_row;
203

            
204
3
      element_node = json_array_get_element (array, i);
205
3
      if (! JSON_NODE_HOLDS_ARRAY (node))
206
        continue;
207

            
208
3
      new_row = new_row_array ();
209
3
      ipuz_guesses_parse_row (new_row, element_node);
210
3
      if (new_row->len == 0)
211
        {
212
          g_array_unref (new_row);
213
          continue;
214
        }
215
3
      g_array_append_val (guesses->cells, new_row);
216
3
      guesses->columns = MAX (guesses->columns, new_row->len);
217
    }
218
1
  guesses->rows = guesses->cells->len;
219
}
220

            
221
IPuzGuesses *
222
1
ipuz_guesses_new_from_json (JsonNode  *root,
223
                            GError   **error)
224
{
225
1
  IPuzGuesses *guesses = NULL;
226
  JsonNode *node;
227
  JsonObject *obj;
228

            
229
1
  g_return_val_if_fail (root != NULL, NULL);
230

            
231
1
  if (!JSON_NODE_HOLDS_OBJECT (root))
232
    {
233
      if (error)
234
        *error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_INVALID_FILE, "The first element isn't an object");
235
      return NULL;
236
    }
237
1
  obj = json_node_get_object (root);
238

            
239
1
  guesses = ipuz_guesses_new ();
240
1
  node = json_object_get_member (obj, "saved");
241
1
  ipuz_guesses_parse_saved (guesses, node);
242

            
243
1
  return guesses;
244
}
245

            
246
IPuzGuesses *
247
1
ipuz_guesses_new_from_file (const char  *filename,
248
                            GError     **error)
249

            
250
{
251
1
  GError *tmp_error = NULL;
252
1
  g_autoptr(JsonParser) parser = NULL;
253

            
254
1
  g_return_val_if_fail (filename != NULL, NULL);
255

            
256
1
  parser = json_parser_new ();
257
1
  json_parser_load_from_file (parser, filename, &tmp_error);
258
1
  if (tmp_error != NULL)
259
    {
260
      g_propagate_error (error, tmp_error);
261
      return NULL;
262
    }
263

            
264
1
  return ipuz_guesses_new_from_json (json_parser_get_root (parser), error);
265
}
266

            
267
IPuzGuesses *
268
ipuz_guesses_new_from_stream (GInputStream   *stream,
269
                              GCancellable   *cancellable,
270
                              GError        **error)
271
{
272
  GError *tmp_error = NULL;
273
  JsonParser *parser;
274

            
275
  g_return_val_if_fail (stream != NULL, NULL);
276

            
277
  parser = json_parser_new ();
278
  json_parser_load_from_stream (parser, stream, cancellable, &tmp_error);
279
  if (tmp_error != NULL)
280
    {
281
      g_propagate_error (error, tmp_error);
282
      return NULL;
283
    }
284

            
285
  return ipuz_guesses_new_from_json (json_parser_get_root (parser), error);
286

            
287
}
288

            
289

            
290
/* The variable naming in this function is confusing. We persist.. */
291
static JsonNode *
292
ipuz_guesses_to_json (IPuzGuesses *guesses)
293
{
294
  JsonNode *root, *column_node;
295
  JsonObject *root_obj;
296
  JsonArray *column_node_array;
297
  guint row, column;
298

            
299
  root = json_node_new (JSON_NODE_OBJECT);
300
  root_obj = json_object_new();
301
  json_node_take_object (root, root_obj);
302

            
303
  if (guesses->puzzle_id)
304
    {
305
      json_object_set_string_member (root_obj, "puzzle-id", guesses->puzzle_id);
306
    }
307

            
308

            
309
  column_node = json_node_new (JSON_NODE_ARRAY);
310
  column_node_array = json_array_new ();
311
  json_node_take_array (column_node, column_node_array);
312

            
313
  json_object_set_member (root_obj, "saved", column_node);
314

            
315
  for (row = 0; row < guesses->rows; row++)
316
    {
317
      GArray *row_array = g_array_index (guesses->cells, GArray *, row);
318
      JsonNode *row_node = json_node_new (JSON_NODE_ARRAY);
319
      JsonArray *row_node_array = json_array_new ();
320
      json_node_take_array (row_node, row_node_array);
321

            
322
      json_array_add_element (column_node_array, row_node);
323

            
324
      for (column = 0; column < row_array->len; column++)
325
        {
326
          IPuzGuessCell *cell = &(g_array_index (row_array, IPuzGuessCell, column));
327

            
328
          switch (cell->cell_type)
329
            {
330
            case IPUZ_CELL_NULL:
331
              json_array_add_null_element (row_node_array);
332
              break;
333
            case IPUZ_CELL_BLOCK:
334
              json_array_add_string_element (row_node_array, _IPUZ_DEFAULT_BLOCK);
335
              break;
336
            case IPUZ_CELL_NORMAL:
337
              if (cell->guess == NULL)
338
                json_array_add_string_element (row_node_array, "");
339
              else
340
                json_array_add_string_element (row_node_array, cell->guess);
341
              break;
342
            }
343
        }
344
    }
345

            
346
  return root;
347
}
348

            
349
gboolean
350
ipuz_guesses_save_to_file (IPuzGuesses  *guesses,
351
                           const gchar  *filename,
352
                           GError      **error)
353
{
354
  g_autoptr (JsonNode) root = NULL;
355
  g_autoptr (JsonGenerator) generator = NULL;
356

            
357
  g_return_val_if_fail (guesses != NULL, FALSE);
358
  g_return_val_if_fail (filename != NULL, FALSE);
359

            
360
  generator = json_generator_new ();
361
  json_generator_set_pretty (generator, TRUE);
362
  root = ipuz_guesses_to_json (guesses);
363
  json_generator_set_root (generator, root);
364
  if (! root)
365
    return FALSE;
366

            
367
  return json_generator_to_file (generator, filename, error);
368
}
369

            
370

            
371
IPuzGuesses *
372
ipuz_guesses_ref (IPuzGuesses *guesses)
373
{
374
  g_return_val_if_fail (guesses != NULL, NULL);
375

            
376
  g_ref_count_inc (&guesses->ref_count);
377

            
378
  return guesses;
379
}
380

            
381
void
382
41
ipuz_guesses_unref (IPuzGuesses *guesses)
383
{
384
41
    if (guesses == NULL)
385
39
    return;
386

            
387
2
  if (!g_ref_count_dec (&guesses->ref_count))
388
    return;
389

            
390
  /* Free data */
391
2
  g_array_unref (guesses->cells);
392
2
  g_free (guesses->puzzle_id);
393
2
  g_free (guesses);
394
}
395

            
396
IPuzGuesses *
397
2
ipuz_guesses_copy (IPuzGuesses *src)
398
{
399
  IPuzGuesses *dest;
400
  guint row, column;
401

            
402
2
  if (src == NULL)
403
2
    return NULL;
404

            
405
  dest = ipuz_guesses_new ();
406

            
407
  dest->rows = src->rows;
408
  dest->columns = src->columns;
409

            
410
  dest->puzzle_id = g_strdup (src->puzzle_id);
411

            
412
  for (row = 0; row < src->rows; row++)
413
    {
414
      GArray *src_row = g_array_index (src->cells, GArray *, row);
415
      GArray *dest_row = new_row_array ();
416
      g_array_set_size (dest_row, src->columns);
417

            
418
      g_array_append_val (dest->cells, dest_row);
419

            
420
      for (column = 0; column < src->columns; column++)
421
        {
422
          IPuzGuessCell *src_cell = &(g_array_index (src_row, IPuzGuessCell, column));
423
          IPuzGuessCell *dest_cell = &(g_array_index (dest_row, IPuzGuessCell, column));
424

            
425
          dest_cell->cell_type = src_cell->cell_type;
426
          if (src_cell->cell_type == IPUZ_CELL_NORMAL)
427
            dest_cell->guess = g_strdup (src_cell->guess);
428
        }
429
    }
430
  return dest;
431
}
432

            
433
gboolean
434
5
ipuz_guesses_equal (IPuzGuesses *a,
435
                    IPuzGuesses *b)
436
{
437
  guint row, column;
438

            
439
5
  if (a == NULL && b == NULL)
440
3
    return TRUE;
441

            
442
2
  if (a == NULL || b == NULL)
443
    return FALSE;
444

            
445
2
  if (a->rows != b->rows || a->columns != b->columns)
446
    {
447
      return FALSE;
448
    }
449

            
450

            
451
5
  for (row = 0; row < a->rows; row++)
452
   {
453
     GArray *row_a, *row_b;
454

            
455
4
     row_a = g_array_index (a->cells, GArray *, row);
456
4
     row_b = g_array_index (b->cells, GArray *, row);
457
13
      for (column = 0; column < a->columns; column++)
458
        {
459
          IPuzGuessCell cell_a, cell_b;
460
10
          cell_a = g_array_index (row_a, IPuzGuessCell, column);
461
10
          cell_b = g_array_index (row_b, IPuzGuessCell, column);
462

            
463
10
          if (cell_a.cell_type != cell_b.cell_type)
464
1
            return FALSE;
465

            
466
18
          if (cell_a.cell_type == IPUZ_CELL_NORMAL &&
467
8
              g_strcmp0 (cell_a.guess, cell_b.guess) != 0)
468
1
            return FALSE;
469
        }
470
   }
471
1
  return TRUE;
472
}
473

            
474
guint
475
ipuz_guesses_get_width (IPuzGuesses *guesses)
476
{
477
  g_return_val_if_fail (guesses != NULL, 0);
478

            
479
  return guesses->columns;
480
}
481

            
482
guint
483
ipuz_guesses_get_height (IPuzGuesses *guesses)
484
{
485
  g_return_val_if_fail (guesses != NULL, 0);
486

            
487
  return guesses->rows;
488
}
489

            
490

            
491
const gchar *
492
ipuz_guesses_get_guess (IPuzGuesses   *guesses,
493
                        IPuzCellCoord  coord)
494
{
495
  GArray *row_array;
496
  IPuzGuessCell *cell;
497

            
498
  g_return_val_if_fail (guesses != NULL, NULL);
499

            
500
  if (coord.row >= guesses->rows || coord.column >= guesses->columns)
501
    return NULL;
502

            
503
  row_array = g_array_index (guesses->cells, GArray *, coord.row);
504
  g_assert (row_array);
505

            
506
  cell = &(g_array_index (row_array, IPuzGuessCell, coord.column));
507
  return cell->guess;
508
}
509

            
510
void
511
3
ipuz_guesses_set_guess (IPuzGuesses   *guesses,
512
                        IPuzCellCoord  coord,
513
                        const gchar   *guess)
514
{
515
  GArray *row_array;
516
  IPuzGuessCell *cell;
517

            
518
3
  g_return_if_fail (guesses != NULL);
519

            
520
3
  if (coord.row >= guesses->rows || coord.column >= guesses->columns)
521
    return;
522

            
523
3
  row_array = g_array_index (guesses->cells, GArray *, coord.row);
524
3
  g_assert (row_array);
525

            
526
3
  cell = &(g_array_index (row_array, IPuzGuessCell, coord.column));
527

            
528
3
  g_return_if_fail (cell->cell_type == IPUZ_CELL_NORMAL);
529
3
  g_clear_pointer (&cell->guess, g_free);
530
3
  cell->guess = g_strdup (guess);
531
}
532

            
533
/* FIXME(cleanup): We should wrie an internal get_cell function to
534
 * cleanup these getters.
535
 */
536
IPuzCellCellType
537
ipuz_guesses_get_cell_type (IPuzGuesses   *guesses,
538
                            IPuzCellCoord  coord)
539
{
540
  GArray *row_array;
541
  IPuzGuessCell *cell;
542

            
543
  g_return_val_if_fail (guesses != NULL, IPUZ_CELL_NORMAL);
544

            
545
  if (coord.row >= guesses->rows || coord.column >= guesses->columns)
546
    return IPUZ_CELL_NORMAL;
547

            
548
  row_array = g_array_index (guesses->cells, GArray *, coord.row);
549
  g_assert (row_array);
550

            
551
  cell = &(g_array_index (row_array, IPuzGuessCell, coord.column));
552
  return cell->cell_type;
553
}
554

            
555
/* Returns the percentage filled out. Not the percentage correct
556
 */
557
gfloat
558
ipuz_guesses_get_percent (IPuzGuesses *guesses)
559
{
560
  gint guessed = 0;
561
  gint total = 0;
562
  guint row, column;
563

            
564
  g_return_val_if_fail (guesses != NULL, 0.0);
565

            
566
  for (row = 0; row < guesses->rows; row++)
567
    {
568
      GArray *row_array = g_array_index (guesses->cells, GArray *, row);
569
      for (column = 0; column < guesses->columns; column++)
570
        {
571
          IPuzGuessCell *cell = &(g_array_index (row_array, IPuzGuessCell, column));
572
          if (cell->cell_type == IPUZ_CELL_NORMAL)
573
            {
574
              total ++;
575
              if (cell->guess != NULL)
576
                guessed++;
577
            }
578
        }
579
    }
580

            
581
  if (total == 0)
582
    return 0.0;
583

            
584
  return ((gfloat) guessed) / total;
585
}
586

            
587

            
588
/**
589
 * ipuz_guesses_get_checksum:
590
 * @guesses: An `IPuzGuess`
591
 * @salt: used to seed the checksum, or NULL
592
 *
593
 * Returns a SHA1 HASH representing the current state of the
594
 * puzzle. Used to
595
 *
596
 * Returns (transfer full): a newly allocated checksum of the solution
597
 **/
598
gchar *
599
ipuz_guesses_get_checksum (IPuzGuesses *guesses,
600
                           const gchar *salt)
601
{
602
  g_autoptr (GString) str = NULL;
603
  guint row, column;
604

            
605
  g_return_val_if_fail (guesses != NULL, NULL);
606

            
607
  str = g_string_new (NULL);
608
  for (row = 0; row < guesses->rows; row++)
609
    {
610
      GArray *row_array = g_array_index (guesses->cells, GArray *, row);
611
      for (column = 0; column < guesses->columns; column++)
612
        {
613
          IPuzGuessCell *cell = &(g_array_index (row_array, IPuzGuessCell, column));
614
          if (cell->cell_type == IPUZ_CELL_NORMAL)
615
            {
616
              if (cell->guess != NULL)
617
                g_string_append (str, cell->guess);
618
              else
619
                g_string_append (str, _IPUZ_DEFAULT_EMPTY);
620
            }
621
        }
622
    }
623

            
624
  if (salt)
625
    g_string_append (str, salt);
626

            
627
  return g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, str->len);
628
}
629

            
630
void
631
ipuz_guesses_print (IPuzGuesses *guesses)
632
{
633
  guint row, column;
634

            
635
  g_return_if_fail (guesses != NULL);
636

            
637
  for (guint i = 0; i < guesses->columns + 1; i++)
638
    g_print ("██");
639
  g_print ("\n");
640
  for (row = 0; row < guesses->rows; row ++)
641
    {
642
      g_print ("█");
643
      for (column = 0; column < guesses->columns; column ++)
644
        {
645
          IPuzCellCoord coord = { .row = row, .column = column };
646

            
647
          switch (ipuz_guesses_get_cell_type (guesses, coord))
648
            {
649
            case IPUZ_CELL_BLOCK:
650
              g_print ("▓▓");
651
              break;
652
            case IPUZ_CELL_NULL:
653
              g_print ("▞▚");
654
              break;
655
            case IPUZ_CELL_NORMAL:
656
              g_print ("  ");
657
              break;
658
            }
659
        }
660
      g_print ("█\n█");
661
      for (column = 0; column < guesses->columns; column ++)
662
        {
663
          IPuzCellCoord coord = { .row = row, .column = column };
664
          const gchar *guess = NULL;
665

            
666
          guess = ipuz_guesses_get_guess (guesses, coord);
667

            
668
          switch (ipuz_guesses_get_cell_type (guesses, coord))
669
            {
670
            case IPUZ_CELL_BLOCK:
671
              g_print ("▓▓");
672
              break;
673
            case IPUZ_CELL_NULL:
674
              g_print ("▚▞");
675
              break;
676
            case IPUZ_CELL_NORMAL:
677
              if (guess)
678
                g_print (" %s", guess);
679
              else
680
                g_print ("  ");
681
              break;
682
            }
683
        }
684
      g_print ("█\n");
685
    }
686
  for (column = 0; column < guesses->columns + 1; column++)
687
    g_print ("██");
688
  g_print ("\n\n");
689
}