1
/* ipuz-board.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 <libipuz/ipuz-board.h>
23

            
24
struct _IPuzBoard
25
{
26
  GObject parent_instance;
27

            
28
  GArray *cells;
29
  guint rows;
30
  guint columns;
31
};
32

            
33
static void ipuz_board_init       (IPuzBoard      *self);
34
static void ipuz_board_class_init (IPuzBoardClass *klass);
35
static void ipuz_board_finalize   (GObject        *object);
36

            
37

            
38
6
G_DEFINE_TYPE (IPuzBoard, ipuz_board, G_TYPE_OBJECT);
39

            
40

            
41
/*
42
 * Class Methods
43
 */
44

            
45
/* This is called from GArray, which gives us the address of one of its
46
 * elements to clear.  Since we are storing (GArray *) each element,
47
 * we need double indirection here.
48
 */
49
static void
50
386
cells_clear_func (GArray **cell_row)
51
{
52
386
  g_array_free (*cell_row, TRUE);
53
386
  *cell_row = NULL;
54
386
}
55

            
56
static void
57
39
ipuz_board_init (IPuzBoard *self)
58
{
59
39
  g_return_if_fail (self != NULL);
60

            
61
39
  self->cells = g_array_new (FALSE, TRUE, sizeof (GArray*));
62
39
  g_array_set_clear_func (self->cells, (GDestroyNotify) cells_clear_func);
63
39
  self->rows = 0;
64
39
  self->columns = 0;
65
}
66

            
67
static void
68
6
ipuz_board_class_init (IPuzBoardClass *klass)
69
{
70
6
  GObjectClass *object_class  = G_OBJECT_CLASS(klass);
71

            
72
6
  object_class->finalize = ipuz_board_finalize;
73
6
}
74

            
75
static void
76
39
ipuz_board_finalize (GObject *object)
77
{
78
  IPuzBoard *self;
79

            
80
39
  g_return_if_fail (object != NULL);
81

            
82
39
  self = IPUZ_BOARD (object);
83

            
84
39
  g_array_free (self->cells, TRUE);
85

            
86
39
  G_OBJECT_CLASS (ipuz_board_parent_class)->finalize (object);
87
}
88

            
89
/*
90
 * Public functions
91
 */
92

            
93
IPuzBoard *
94
39
ipuz_board_new ()
95
{
96
39
  return (IPuzBoard *) g_object_new (IPUZ_TYPE_BOARD, NULL);
97
}
98

            
99
void
100
2
ipuz_board_build_puzzle (IPuzBoard   *board,
101
                         JsonBuilder *builder,
102
                         const char  *block,
103
                         const char  *empty)
104
{
105
  guint r, c;
106

            
107
2
  g_return_if_fail (IPUZ_IS_BOARD (board));
108
2
  g_return_if_fail (JSON_IS_BUILDER (builder));
109

            
110
2
  if (board->rows == 0 || board->columns == 0)
111
    return;
112

            
113
2
  json_builder_set_member_name (builder, "puzzle");
114
2
  json_builder_begin_array (builder);
115
29
  for (r = 0; r < board->rows; r++)
116
    {
117
27
      json_builder_begin_array (builder);
118
476
      for (c = 0; c < board->columns; c++)
119
        {
120
449
          IPuzCellCoord coord = { .row = r, .column = c };
121
449
          IPuzCell *cell = ipuz_board_get_cell (board, coord);
122

            
123
449
          ipuz_cell_build (cell, builder, FALSE, block, empty);
124
        }
125
27
      json_builder_end_array (builder);
126
    }
127
2
  json_builder_end_array (builder);
128
}
129

            
130

            
131
void
132
2
ipuz_board_build_solution (IPuzBoard   *board,
133
                           JsonBuilder *builder,
134
                           const char  *block)
135
{
136
  guint r, c;
137

            
138
2
  g_return_if_fail (IPUZ_IS_BOARD (board));
139
2
  g_return_if_fail (JSON_IS_BUILDER (builder));
140

            
141
2
  if (board->rows == 0 || board->columns == 0)
142
    return;
143

            
144
2
  json_builder_set_member_name (builder, "solution");
145
2
  json_builder_begin_array (builder);
146
29
  for (r = 0; r < board->rows; r++)
147
    {
148
27
      json_builder_begin_array (builder);
149
476
      for (c = 0; c < board->columns; c++)
150
        {
151
449
          IPuzCellCoord coord = { .row = r, .column = c };
152
449
          IPuzCell *cell = ipuz_board_get_cell (board, coord);
153

            
154
449
          ipuz_cell_build (cell, builder, TRUE, block, NULL);
155
        }
156
27
      json_builder_end_array (builder);
157
    }
158
2
  json_builder_end_array (builder);
159
}
160

            
161
/**
162
 * ipuz_board_equal:
163
 *
164
 * @a: First board to compare.
165
 * @b: Second board to compare.
166
 *
167
 * Returns: whether the boards have the same dimensions and their
168
 * respective cells are equal.
169
 */
170
gboolean
171
9
ipuz_board_equal (IPuzBoard *a,
172
                  IPuzBoard *b)
173
{
174
  guint r, c;
175

            
176
9
  g_assert (IPUZ_IS_BOARD (a));
177
9
  g_assert (IPUZ_IS_BOARD (b));
178

            
179
9
  if (a->rows != b->rows || a->columns != b->columns)
180
    {
181
      return FALSE;
182
    }
183

            
184
93
  for (r = 0; r < a->rows; r++)
185
    {
186
1343
      for (c = 0; c < a->columns; c++)
187
        {
188
1259
          IPuzCellCoord coord = { .row = r, .column = c };
189
1259
          IPuzCell *cell_a = ipuz_board_get_cell (a, coord);
190
1259
          IPuzCell *cell_b = ipuz_board_get_cell (b, coord);
191
1259
          if (!ipuz_cell_equal (cell_a, cell_b))
192
            {
193
2
              return FALSE;
194
            }
195
        }
196
    }
197

            
198
7
  return TRUE;
199
}
200

            
201
void
202
39
ipuz_board_resize (IPuzBoard *board,
203
                   guint      new_width,
204
                   guint      new_height)
205
{
206
  guint old_width, old_height;
207

            
208
39
  g_return_if_fail (IPUZ_IS_BOARD (board));
209
39
  g_return_if_fail (new_width > 0);
210
39
  g_return_if_fail (new_height > 0);
211

            
212
  /* Calculate the old dimensions from the existing array.
213
   */
214
39
  old_height = board->rows;
215
39
  old_width = board->columns;
216

            
217
39
  if (new_width == old_width && new_height == old_height)
218
    return;
219

            
220
  /* If we are growing vertically, add more rows. Otherwise, if we're shrinking,
221
   * we count on g_array_set_size to do the right thing for us. */
222
39
  if (new_height > old_height)
223
    {
224
425
      for (guint i = 0; i < new_height - old_height; i++)
225
        {
226
          GArray *new_row;
227
386
          new_row = g_array_new (FALSE, TRUE, sizeof (IPuzCell));
228
386
          g_array_set_clear_func (new_row, (GDestroyNotify) ipuz_cell_clear);
229
386
          g_array_append_val (board->cells, new_row);
230
        }
231
    }
232
  else
233
    {
234
      g_array_set_size (board->cells, new_height);
235
    }
236

            
237
  /* Resize all the rows to be the right size. This will clear memory if rows
238
   * shrink or fill it with empty cells if they grow.
239
   */
240
428
  for (guint i = 0; i < board->cells->len; i++)
241
    {
242
      GArray *row;
243
389
      row = g_array_index (board->cells, GArray *, i);
244
389
      g_array_set_size (row, new_width);
245
    }
246

            
247
39
  board->rows = new_height;
248
39
  board->columns = new_width;
249
}
250

            
251
static void
252
346
ipuz_board_parse_puzzle_row (GArray      *row,
253
                             JsonArray   *array,
254
                             const gchar *block,
255
                             const gchar *empty)
256
{
257
  guint n_rows, array_len;
258

            
259
346
  g_return_if_fail (row != NULL);
260
346
  g_return_if_fail (array != NULL);
261
346
  g_return_if_fail (block != NULL);
262

            
263
346
  array_len = json_array_get_length (array);
264
346
  n_rows = row->len;
265

            
266
5202
  for (guint i = 0; i < MIN (n_rows, array_len); i++)
267
    {
268
      JsonNode *node;
269
      IPuzCell *cell;
270

            
271

            
272
4856
      node = json_array_get_element (array, i);
273
4856
      cell = & (g_array_index (row, IPuzCell, i));
274

            
275

            
276
4856
      ipuz_cell_parse_puzzle (cell, node, block, empty);
277
    }
278
}
279

            
280
void
281
32
ipuz_board_parse_puzzle (IPuzBoard   *board,
282
                         JsonNode    *node,
283
                         const gchar *block,
284
                         const gchar *empty)
285
{
286
  JsonArray *array;
287
  guint array_len;
288

            
289
32
  g_return_if_fail (IPUZ_IS_BOARD (board));
290
32
  g_return_if_fail (node != NULL);
291
32
  g_return_if_fail (block != NULL);
292
32
  g_return_if_fail (empty != NULL);
293

            
294
  /* bail out on anything other than an JsonArray */
295
32
  if (! JSON_NODE_HOLDS_ARRAY (node))
296
    return;
297

            
298
  /* bail out on anything other than an JsonArray */
299
32
  if (! JSON_NODE_HOLDS_ARRAY (node))
300
    return;
301

            
302
32
  array = json_node_get_array (node);
303
32
  array_len = json_array_get_length (array);
304

            
305
378
  for (guint i = 0; i < MIN (board->rows, array_len); i++)
306
    {
307
      JsonNode *row_node;
308
346
      row_node = json_array_get_element (array, i);
309
346
      if (JSON_NODE_HOLDS_ARRAY (row_node))
310
346
        ipuz_board_parse_puzzle_row (g_array_index (board->cells, GArray *, i),
311
                                     json_node_get_array (row_node),
312
                                     block,
313
                                     empty);
314
    }
315
}
316

            
317
/*
318
 * We look for valid strings that aren't in the block element, and are in the
319
 * charset if it exists.
320
 */
321
static void
322
338
ipuz_board_parse_solution_row (GArray      *row,
323
                               guint        columns,
324
                               JsonArray   *array,
325
                               const gchar *block,
326
                               const gchar *charset)
327
{
328
  guint array_len;
329

            
330
338
  g_return_if_fail (row != NULL);
331
338
  g_return_if_fail (array != NULL);
332
338
  g_return_if_fail (block != NULL);
333

            
334
338
  array_len = json_array_get_length (array);
335

            
336
5154
  for (guint i = 0; i < MIN (columns, array_len); i++)
337
    {
338
      JsonNode *node;
339
      IPuzCell *cell;
340

            
341
4816
      cell =  & (g_array_index (row, IPuzCell, i));
342
4816
      node = json_array_get_element (array, i);
343
4816
      ipuz_cell_parse_solution (cell, node, block, charset);
344
    }
345
}
346

            
347
void
348
30
ipuz_board_parse_solution (IPuzBoard   *board,
349
                           JsonNode    *node,
350
                           const gchar *block,
351
                           const gchar *charset)
352
{
353
  JsonArray *array;
354
  guint array_len;
355

            
356
30
  g_return_if_fail (IPUZ_IS_BOARD (board));
357
30
  g_return_if_fail (node != NULL);
358
30
  g_return_if_fail (block != NULL);
359

            
360
  /* bail out on anything other than an JsonArray */
361
30
  if (! JSON_NODE_HOLDS_ARRAY (node))
362
    return;
363

            
364
30
  array = json_node_get_array (node);
365
30
  array_len = json_array_get_length (array);
366

            
367
368
  for (guint i = 0; i < MIN (board->rows, array_len); i++)
368
    {
369
      JsonNode *row_node;
370
338
      row_node = json_array_get_element (array, i);
371
338
      if (JSON_NODE_HOLDS_ARRAY (row_node))
372
338
        ipuz_board_parse_solution_row (g_array_index (board->cells, GArray *, i),
373
                                       board->columns,
374
                                       json_node_get_array (row_node),
375
                                       block,
376
                                       charset);
377
    }
378
}
379

            
380
/**
381
 * ipuz_board_get_cell:
382
 *
383
 * @board: An `IPuzBoard`
384
 * @coord: Coordinates for the cell.
385
 *
386
 * Retrieves the cell at @coord. If the coordinates are
387
 * outside the bounds of the board then it will return %NULL
388
 *
389
 * The coordinate system of the @board is similar to that of s spreadsheet. The
390
 * origin is the upper left corner. Row's increase vertically downward, and
391
 * columns increase horizontally.
392
 *
393
 * Returns: (nullable) (transfer none): The cell at @coord.
394
 **/
395
IPuzCell *
396
43210
ipuz_board_get_cell (IPuzBoard     *board,
397
                     IPuzCellCoord  coord)
398
{
399
  GArray *row_array;
400

            
401
43210
  g_return_val_if_fail (IPUZ_IS_BOARD (board), NULL);
402

            
403
43210
  if (coord.row >= board->rows || coord.column >= board->columns)
404
362
    return NULL;
405

            
406
42848
  row_array = g_array_index (board->cells, GArray *, coord.row);
407
42848
  g_assert (row_array);
408

            
409
42848
  return &(g_array_index (row_array, IPuzCell, coord.column));
410
}
411

            
412

            
413
/**
414
 * ipuz_board_get_width:
415
 * @board: An `IpuzBoard`
416
 *
417
 * Returns the width of @board
418
 *
419
 * Returns: The width of @board
420
 **/
421
guint
422
1
ipuz_board_get_width (IPuzBoard *board)
423
{
424
1
  g_return_val_if_fail (IPUZ_IS_BOARD (board), 0);
425

            
426
1
  return board->columns;
427
}
428

            
429
/**
430
 * ipuz_board_get_height:
431
 * @board: An `IpuzBoard`
432
 *
433
 * Returns the height of @board
434
 *
435
 * Returns: The height of @board
436
 **/
437
guint
438
1
ipuz_board_get_height (IPuzBoard *board)
439
{
440
1
  g_return_val_if_fail (IPUZ_IS_BOARD (board), 0);
441

            
442
1
  return board->rows;
443
}
444

            
445
/**
446
 * ipuz_board_get_cell:
447
 *
448
 * @board: An `IPuzBoard`
449
 * @clue: The @clue to locate
450
 * @coord: Location to write the coordinates of first cell of @clue, or $NULL
451
 *
452
 * Retrieves the first cell of @clue from @board. If coord is not %NULL, then
453
 * it will be populated with the coordinates of the first cell.
454
 *
455
 * Returns: (nullable) (transfer none): The first cell of @clue, or %NULL
456
 **/
457
IPuzCell *
458
191
ipuz_board_get_cell_by_clue (IPuzBoard     *board,
459
                             IPuzClue      *clue,
460
                             IPuzCellCoord *coord)
461
{
462
  const GArray *cells;
463
  guint r, c;
464

            
465
191
  g_return_val_if_fail (IPUZ_IS_BOARD (board), NULL);
466
191
  g_return_val_if_fail (clue != NULL, NULL);
467

            
468
191
  cells = ipuz_clue_get_cells (clue);
469
191
  g_assert (cells);
470

            
471
  /* If the cells on clue have been populated, return the first one */
472
191
  if (cells->len > 0)
473
    {
474
      IPuzCellCoord *cell_coord;
475
      cell_coord = &g_array_index (cells, IPuzCellCoord, 0);
476
      if (coord)
477
        *coord = *cell_coord;
478

            
479
      return ipuz_board_get_cell (board, *cell_coord);
480
    }
481

            
482
  /* Note: This can be called before clue->cells has been set.
483
   * As a result, we walk the board looking for the first match */
484
1055
   for (r = 0; r < board->rows; r++)
485
    {
486
14886
      for (c = 0; c < board->columns; c++)
487
        {
488
14022
          IPuzCellCoord cell_coord = { .row = r, .column = c };
489
14022
          IPuzCell *cell = ipuz_board_get_cell (board, cell_coord);
490
          gint cell_number, clue_number;
491
          const gchar *cell_label, *clue_label;
492

            
493
          /* Heuristic. If the labels are non-null and match, or they share a
494
           * number that's non 0, then they match.
495
           * FIXME: We should probably refactor clue / cell equality to a
496
           * separate public function.
497
           */
498
14022
          cell_number = ipuz_cell_get_number (cell);
499
14022
          clue_number = ipuz_clue_get_number (clue);
500
14022
          cell_label = ipuz_cell_get_label (cell);
501
14022
          clue_label = ipuz_clue_get_label (clue);
502

            
503
14022
          if ((cell_label && (g_strcmp0 (cell_label, clue_label) == 0))
504
14022
              || ((cell_number > 0) && (cell_number == clue_number)))
505
            {
506
191
              if (coord)
507
                {
508
191
                  coord->row = r;
509
191
                  coord->column = c;
510
                }
511
191
              return cell;
512
            }
513
        }
514
    }
515

            
516
  return NULL;
517
}