]> git.cworth.org Git - loudgame/blob - loa-board.c
9478731e29cbb9455dd9da7f492239e5e9f052f5
[loudgame] / loa-board.c
1 /*
2  * Copyright (C) 2008 Carl Worth
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see http://www.gnu.org/licenses/ .
16  *
17  * Author: Carl Worth <cworth@cworth.org>
18  */
19
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <assert.h>
24 #include <string.h>
25 #include <ctype.h>
26
27 #include <glib.h>
28
29 #include "loa-board.h"
30
31 static loa_bool_t
32 loa_move_is_valid (const loa_move_t *move)
33 {
34     return (move->x1 >= 0 && move->x1 < LOA_BOARD_SIZE &&
35             move->y1 >= 0 && move->y1 < LOA_BOARD_SIZE &&
36             move->x2 >= 0 && move->x2 < LOA_BOARD_SIZE &&
37             move->y2 >= 0 && move->y2 < LOA_BOARD_SIZE);
38 }
39
40 const char *
41 loa_move_to_string (const loa_move_t *move)
42 {
43 #define LOA_MOVE_STRING_SIZE 6
44     static char move_string[LOA_MOVE_STRING_SIZE];
45
46     if (! loa_move_is_valid (move)) {
47         strcpy (move_string, "***");
48         return move_string;
49     }
50
51     snprintf (move_string, LOA_MOVE_STRING_SIZE,
52               "%c%d%c%c%d",
53               'a' + move->x1, LOA_BOARD_SIZE - move->y1,
54               move->is_capture ? 'x' : '-',
55               'a' + move->x2, LOA_BOARD_SIZE - move->y2);
56
57     return move_string;
58 }
59
60 loa_bool_t
61 loa_move_init_from_string (loa_move_t *move, const char *string)
62 {
63     char xc1, xc2, sep;
64     int x1, y1, x2, y2;
65     int matched;
66
67     /* Avoid returning uninitialized data on error. */
68     move->x1 = 0;
69     move->y1 = 0;
70     move->x2 = 0;
71     move->y2 = 0;
72     move->is_capture = 0;
73
74     matched = sscanf (string, " %c%d%c%c%d", &xc1, &y1, &sep, &xc2, &y2);
75     if (matched != 5) {
76         printf ("Matched only %d fields of %s\n", matched, string);
77         return FALSE;
78     }
79
80     x1 = tolower (xc1) - 'a';
81     x2 = tolower (xc2) - 'a';
82     y1 = LOA_BOARD_SIZE - y1;
83     y2 = LOA_BOARD_SIZE - y2;
84
85     if (x1 < 0 || x1 >= LOA_BOARD_SIZE ||
86         y1 < 0 || y1 >= LOA_BOARD_SIZE ||
87         x2 < 0 || x2 >= LOA_BOARD_SIZE ||
88         y2 < 0 || y2 >= LOA_BOARD_SIZE)
89     {
90         return FALSE;
91     }
92
93     if (sep != '-' && sep != 'x' && sep != 'X')
94         return FALSE;
95
96     move->x1 = x1;
97     move->y1 = y1;
98     move->x2 = x2;
99     move->y2 = y2;
100
101     if (sep == 'x' || sep == 'X')
102         move->is_capture = TRUE;
103     else
104         move->is_capture = FALSE;
105
106     return TRUE;
107 }
108
109 /* Given an (x,y) position on the board, return the index of the array
110  * used to count pieces in diagonal rows running from upper-left to
111  * lower-right, (like a grave accent: à or like a backslash: \).
112  *
113  * This is the array to look into when a move occurs with dx and dy of
114  * the same sign. */
115 static int
116 _grave_index (int x, int y)
117 {
118     return x - y + LOA_BOARD_SIZE - 1;
119 }
120
121 /* Given an (x,y) position on the board, return the index of the array
122  * used to count pieces in diagonal rows running from lower-left to
123  * upper-right, (like an acute accent: á or like a forward slash: /).
124  *
125  * This is the array to look into when a move occurs with dx and dy of
126  * opposite sign, (one positive, one negative). */
127 static int
128 _acute_index (int x, int y)
129 {
130     return x + y;
131 }
132
133 static void
134 loa_board_add_piece (loa_board_t *board, int x, int y, loa_cell_t cell)
135 {
136     assert (cell == LOA_CELL_BLACK || cell == LOA_CELL_WHITE);
137     assert (board->cells[x][y] == LOA_CELL_EMPTY);
138
139     board->col_pieces[x]++;
140     board->row_pieces[y]++;
141     board->diag_grave_pieces[_grave_index (x, y)]++;
142     board->diag_acute_pieces[_acute_index (x, y)]++;
143
144     board->num_pieces[cell]++;
145
146     board->cells[x][y] = cell;
147 }
148
149 static loa_cell_t
150 loa_board_remove_piece (loa_board_t *board, int x, int y)
151 {
152     loa_cell_t cell;
153
154     cell = board->cells[x][y];
155
156     if (cell == LOA_CELL_EMPTY)
157         return LOA_CELL_EMPTY;
158
159     board->col_pieces[x]--;
160     board->row_pieces[y]--;
161     board->diag_grave_pieces[_grave_index (x, y)]--;
162     board->diag_acute_pieces[_acute_index (x, y)]--;
163
164     board->num_pieces[cell]--;
165
166     board->cells[x][y] = LOA_CELL_EMPTY;
167
168     return cell;
169 }
170
171 void
172 loa_board_init (loa_board_t *board)
173 {
174     board->moves = NULL;
175     board->moves_size = 0;
176
177     loa_board_reset (board);
178 }
179
180 void
181 loa_board_fini (loa_board_t *board)
182 {
183     if (board->moves)
184         free (board->moves);
185 }
186
187 void
188 loa_board_reset (loa_board_t *board)
189 {
190     int i, x, y;
191
192     for (x = 0; x < LOA_BOARD_SIZE; x++)
193         for (y = 0; y < LOA_BOARD_SIZE; y++)
194             board->cells[x][y] = LOA_CELL_EMPTY;
195
196     board->num_pieces[LOA_CELL_BLACK] = 0;
197     board->num_pieces[LOA_CELL_WHITE] = 0;
198
199     for (i = 0; i < LOA_BOARD_SIZE; i++) {
200         board->row_pieces[i] = 0;
201         board->col_pieces[i] = 0;
202     }
203
204     for (i = 0; i < LOA_DIAG_ARRAY_SIZE; i++) {
205         board->diag_grave_pieces[i] = 0;
206         board->diag_acute_pieces[i] = 0;
207     }
208
209     for (i = 1; i < LOA_BOARD_SIZE - 1; i++) {
210         loa_board_add_piece (board, i, 0, LOA_CELL_BLACK);
211         loa_board_add_piece (board, i, LOA_BOARD_SIZE - 1, LOA_CELL_BLACK);
212         loa_board_add_piece (board, 0, i, LOA_CELL_WHITE);
213         loa_board_add_piece (board, LOA_BOARD_SIZE - 1, i, LOA_CELL_WHITE);
214     }
215
216     /* Leave board->moves and board->moves_size as allocated */
217     board->num_moves = 0;
218
219     board->player = LOA_PLAYER_BLACK;
220 }
221
222 static int
223 loa_board_group_size_recursive (loa_board_t *board, int x, int y,
224                                 loa_cell_t cell,
225                                 uint64_t *visited)
226 {
227     uint64_t bit;
228
229     if (x < 0 || y < 0)
230         return 0;
231
232     if (x >= LOA_BOARD_SIZE || y >= LOA_BOARD_SIZE)
233         return 0;
234
235     bit = 1ll << (x * LOA_BOARD_SIZE + y);
236     if (*visited & bit)
237         return 0;
238
239     *visited |= bit;
240
241     if (board->cells[x][y] != cell)
242         return 0;
243
244     return 1 +
245         loa_board_group_size_recursive (board, x-1, y-1, cell, visited) +
246         loa_board_group_size_recursive (board, x-1, y  , cell, visited) +
247         loa_board_group_size_recursive (board, x-1, y+1, cell, visited) +
248         loa_board_group_size_recursive (board, x  , y-1, cell, visited) +
249         loa_board_group_size_recursive (board, x  , y  , cell, visited) +
250         loa_board_group_size_recursive (board, x  , y+1, cell, visited) +
251         loa_board_group_size_recursive (board, x+1, y-1, cell, visited) +
252         loa_board_group_size_recursive (board, x+1, y  , cell, visited) +
253         loa_board_group_size_recursive (board, x+1, y+1, cell, visited);
254 }
255
256 static int
257 loa_board_group_size (loa_board_t *board, int x, int y)
258 {
259     uint64_t visited = 0ll;
260     loa_cell_t cell = board->cells[x][y];
261
262     return loa_board_group_size_recursive (board, x, y, cell, &visited);
263 }
264
265 int
266 loa_board_is_won (loa_board_t *board, int x, int y)
267 {
268     loa_cell_t cell = board->cells[x][y];
269
270     if (cell == LOA_CELL_EMPTY)
271         return 0;
272
273     if (loa_board_group_size (board, x, y) == board->num_pieces[cell])
274         return 1;
275
276     return 0;
277 }
278
279 static loa_bool_t
280 loa_board_move_legal (loa_board_t        *board,
281                       const loa_move_t   *move,
282                       char              **error)
283 {
284     int x, y;
285     int x1, y1, x2, y2;
286     int dx, dy;
287     int step_x, step_y;
288
289     if (! loa_move_is_valid (move))
290     {
291         *error = "Invalid coordinates (not on board)";
292         return FALSE;
293     }
294
295     x1 = move->x1;
296     y1 = move->y1;
297     x2 = move->x2;
298     y2 = move->y2;
299
300     if (board->cells[x1][y1] == LOA_CELL_EMPTY) {
301         *error = "There is no piece there to move";
302         return FALSE;
303     }
304
305     if (board->cells[x1][y1] != board->player) {
306         *error = "You cannot move your opponent's piece";
307         return FALSE;
308     }
309
310     if (board->cells[x2][y2] == board->cells[x1][y1]) {
311         *error = "You cannot capture your own piece";
312         return FALSE;
313     }
314
315     dx = x2 - x1;
316     dy = y2 - y1;
317
318     /* Here's the meat of Lines of Action legaility: Does the distance
319      * moved exactly match the number of pieces (of either color) in
320      * the row, column, or diagonal of the movement. */
321     if (dx == 0) { 
322         /* Column */
323         if (abs (dy) != board->col_pieces[x1]) {
324             *error = "The move distance does not match the number of pieces in that column";
325             return FALSE;
326         }
327     } else if (dy == 0) {
328         /* Row */
329         if (abs (dx) != board->row_pieces[y1]) {
330             *error = "The move distance does not match the number of pieces in that row";
331             return FALSE;
332         }
333     } else {
334         if (abs (dx) != abs (dy)) {
335             *error = "That move is not in a row, column, or diagonal";
336             return FALSE;
337         }
338         /* Diagonal */
339         if ((dx > 0) == (dy > 0)) {
340             if (abs (dx) != board->diag_grave_pieces[_grave_index (x1, y1)]) {
341                 *error = "The move distance does not match the number of pieces in that diagonal";
342                 return FALSE;
343             }
344         } else {
345             if (abs (dx) != board->diag_acute_pieces[_acute_index (x1, y1)]) {
346                 *error = "The move distance does not match the number of pieces in that diagonal";
347                 return FALSE;
348             }
349         }
350     }
351
352     /* Finally, we have to ensure that no opponent pieces are being
353      * jumped. */
354     step_x = dx ? ((dx < 0) ? -1 : +1) : 0;
355     step_y = dy ? ((dy < 0) ? -1 : +1) : 0;
356     for (x = x1 + step_x, y = y1 + step_y;
357          x != x2 || y != y2;
358          x += step_x, y += step_y)
359     {
360         if (board->cells[x][y] != LOA_CELL_EMPTY &&
361             board->cells[x][y] != board->cells[x1][y1])
362         {
363             *error = "You cannot jump an opponent's piece";
364             return FALSE;
365         }
366     }
367
368     return TRUE;
369 }
370
371 static void
372 loa_board_next_player (loa_board_t *board)
373 {
374     if (board->player == LOA_PLAYER_BLACK)
375         board->player = LOA_PLAYER_WHITE;
376     else
377         board->player = LOA_PLAYER_BLACK;
378 }
379
380 /* XXX: Should check for a legal move for the current player and
381  * return FALSE in that case, (that is, it should be illegal to pass
382  * if there's a legal move available). */
383 int
384 loa_board_pass (loa_board_t *board)
385 {
386     loa_board_next_player (board);
387
388     return TRUE;
389 }
390
391 /* Once the move is validated and executed, append it to the moves
392  * array that stores the move history. */
393 static void
394 loa_board_add_move_to_history (loa_board_t      *board,
395                                const loa_move_t *move)
396 {
397     board->num_moves++;
398
399     if (board->num_moves > board->moves_size) {
400         if (board->moves_size)
401             board->moves_size *= 2;
402         else
403             board->moves_size = 20;
404
405         board->moves = realloc (board->moves,
406                                 board->moves_size * sizeof (loa_move_t));
407         if (board->moves == NULL) {
408             fprintf (stderr, "Out of memory.\n");
409             exit (1);
410         }
411     }
412
413     board->moves[board->num_moves - 1] = *move;
414 }
415
416 int
417 loa_board_move (loa_board_t *board,
418                 loa_move_t *move,
419                 char **error)
420 {
421     loa_cell_t cell;
422
423     if (! loa_board_move_legal (board, move, error))
424         return FALSE;
425
426     cell = loa_board_remove_piece (board, move->x1, move->y1);
427     assert (cell == board->player);
428
429     cell = loa_board_remove_piece (board, move->x2, move->y2);
430     if (cell == LOA_CELL_EMPTY) {
431         move->is_capture = FALSE;
432     } else {
433         assert (cell != board->player);
434         move->is_capture = TRUE;
435     }
436
437     loa_board_add_piece (board,
438                          move->x2, move->y2,
439                          board->player);
440
441     loa_board_add_move_to_history (board, move);
442
443     loa_board_next_player (board);
444
445     return TRUE;
446 }
447
448 /* A few different ideas for displaying boards:
449  *
450  * 8│ │●│●│●│●│●│●│ |       8| |*|*|*|*|*|*| |
451  * 7│○│ │ │ │ │ │ │○|       7|o| | | | | | |o|
452  * 6│○│ │ │ │ │ │ │○|       6|o| | | | | | |o|
453  * 5│○│ │ │ │ │ │ │○|       5|o| | | | | | |o|
454  * 4│○│ │ │ │ │ │ │○|       4|o| | | | | | |o|
455  * 3│○│ │ │ │ │ │ │○|       3|o| | | | | | |o|
456  * 2│○│ │ │ │ │ │ │○|       2|o| | | | | | |o|
457  * 1│ │●│●│●│●│●│●│ |       1| |*|*|*|*|*|*| |
458  *   A B C D E F G H      A B C D E F G H
459  *
460  *  ┌───┬───┬───┬───┬───┬───┬───┬───┐   ------------------------------- 
461  * 8│   │ ● │ ● │ ● │ ● │ ● │ ● │   │     8|   | * | * | * | * | * | * |   |
462  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
463  * 7│ ○ │   │   │   │   │   │   │ ○ │     7| o |   |   |   |   |   |   | o |
464  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
465  * 6│ ○ │   │   │   │   │   │   │ ○ │     6| o |   |   |   |   |   |   | o |
466  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
467  * 5│ ○ │   │   │   │   │   │   │ ○ │     5| o |   |   |   |   |   |   | o |
468  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
469  * 4│ ○ │   │   │   │   │   │   │ ○ │     4| o |   |   |   |   |   |   | o |
470  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
471  * 3│ ○ │   │   │   │   │   │   │ ○ │     3| o |   |   |   |   |   |   | o |
472  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
473  * 2│ ○ │   │   │   │   │   │   │ ○ │     2| o |   |   |   |   |   |   | o |
474  *  ├───┼───┼───┼───┼───┼───┼───┼───┤  |---+---+---+---+---+---+---+---|
475  * 1│   │ ● │ ● │ ● │ ● │ ● │ ● │   │     1|   | * | * | * | * | * | * |   |
476  *  └───┴───┴───┴───┴───┴───┴───┴───┘   ------------------------------- 
477  *    A   B   C   D   E   F   G   H        A   B   C   D   E   F   G   H
478  */
479 char *
480 loa_board_to_string (loa_board_t *board)
481 {
482     int x, y;
483     /* In order of BLACK, WHITE, EMPTY */
484     const char* cell_strings[] = {"●","○"," "};
485     const char   board_header[] = "┌───┬───┬───┬───┬───┬───┬───┬───┐";
486     const char     row_header[] = "│ ";
487     const char cell_separator[] =    " │ ";
488     const char     row_footer[] =                                " │";
489     const char  row_separator[] = "├───┼───┼───┼───┼───┼───┼───┼───┤";
490     const char   board_footer[] = "└───┴───┴───┴───┴───┴───┴───┴───┘";
491     char *board_string = g_strdup ("");
492
493 #define APPEND(str) do {                                        \
494     char *_new = g_strconcat (board_string, (str), NULL);       \
495     free (board_string);                                        \
496     board_string = _new;                                        \
497 } while (0)
498
499 #define APPENDF(format, ...) do {                               \
500     char *str = g_strdup_printf (format, ## __VA_ARGS__);       \
501     APPEND (str);                                               \
502     free (str);                                                 \
503 } while (0)
504
505     APPENDF (" %s\n", board_header);
506     for (y = 0; y < LOA_BOARD_SIZE; y++) {
507         APPENDF ("%d%s", LOA_BOARD_SIZE - y, row_header);
508         for (x = 0; x < LOA_BOARD_SIZE; x++) {
509             APPENDF ("%s", cell_strings[board->cells[x][y]]);
510             if (x != LOA_BOARD_SIZE - 1)
511                 APPENDF ("%s", cell_separator);
512         }
513         APPENDF ("%s\n", row_footer);
514         if (y != LOA_BOARD_SIZE -1)
515             APPENDF (" %s\n", row_separator);
516     }
517     APPENDF (" %s\n", board_footer);
518
519     APPENDF ("   ");
520     for (x = 0; x < LOA_BOARD_SIZE; x++) {
521         APPENDF ("%c", 'A' + x);
522         if (x != LOA_BOARD_SIZE - 1)
523             APPENDF ("   ");
524     }
525     APPENDF ("\n");
526
527     return board_string;
528 }