Add support for a 'history' command
authorCarl Worth <cworth@cworth.org>
Mon, 3 Mar 2008 17:48:48 +0000 (09:48 -0800)
committerCarl Worth <cworth@cworth.org>
Mon, 3 Mar 2008 17:48:48 +0000 (09:48 -0800)
lg-loa.c
loa-board.c
loa-board.h

index 4bf623076442ea9d8c9d7e7369fb8a3226f3f355..174914307f71ca72faf74a496b08ce00e2a2d336 100644 (file)
--- a/lg-loa.c
+++ b/lg-loa.c
@@ -37,7 +37,7 @@ typedef struct _loa_game {
 static void
 loa_game_new_game (loa_game_t *game)
 {
-    loa_board_init (&game->board);
+    loa_board_reset (&game->board);
 }
 
 static loa_bool_t
@@ -143,6 +143,17 @@ loa_game_handle_move (loa_game_t *game,
        loudgame_broadcastf (&game->lg, "%s wins", peer);
 }
 
+static void
+loa_game_handle_history (loa_game_t *game,
+                        const char *peer)
+{
+    int i;
+
+    for (i = 0; i < game->board.num_moves; i++)
+       loudgame_sendf (&game->lg, peer, "%s",
+                       loa_move_to_string (&game->board.moves[i]));
+}
+
 static void
 loa_game_handle_pass (loa_game_t *game, const char *peer)
 {
@@ -168,6 +179,7 @@ loa_game_handle_help (loa_game_t *game, const char *peer)
                    "\tmove aNbN\tMove a piece, (eg. 'move b1d3')\n"
                    "\tpass     \t\tSkip a turn (only legal if no moves are possible)\n"
                    "\tnew      \t\tBegin a new game\n"
+                   "\thistory  \t\tShow the move history of the game\n"
                    "\thelp     \t\tThis help message\n"
                    "\trules    \t\tA description of the Lines of Action rules\n"
                    "\n"
@@ -244,6 +256,8 @@ loa_game_handle_message (loudgame_t *lg,
        loa_game_handle_pass (game, peer);
     else if (strcmp (message, "new") == 0)
        loa_game_handle_new (game, peer);
+    else if (strcmp (message, "history") == 0)
+       loa_game_handle_history (game, peer);
     else if (strcmp (message, "help") == 0)
        loa_game_handle_help (game, peer);
     else if (strcmp (message, "rules") == 0)
@@ -261,6 +275,8 @@ loa_game_init (loa_game_t *game, int argc, char *argv[])
     if (err)
        return err;
 
+    loa_board_init (&game->board);
+
     loa_game_new_game (game);
 
     return 0;
index 8ea6f99d36bb45ad758011bee975ef57faf3c86a..21f4302ffaf676283c0e7b1495f5839f191d0194 100644 (file)
  * Author: Carl Worth <cworth@cworth.org>
  */
 
+#include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <assert.h>
+#include <string.h>
+#include <ctype.h>
 
 #include <glib.h>
 
 #include "loa-board.h"
 
+static loa_bool_t
+loa_move_is_valid (const loa_move_t *move)
+{
+    return (move->x1 >= 0 && move->x1 < LOA_BOARD_SIZE &&
+           move->y1 >= 0 && move->y1 < LOA_BOARD_SIZE &&
+           move->x2 >= 0 && move->x2 < LOA_BOARD_SIZE &&
+           move->y2 >= 0 && move->y2 < LOA_BOARD_SIZE);
+}
+
+const char *
+loa_move_to_string (const loa_move_t *move)
+{
+#define LOA_MOVE_STRING_SIZE 6
+    static char move_string[LOA_MOVE_STRING_SIZE];
+
+    if (! loa_move_is_valid (move)) {
+       strcpy (move_string, "***");
+       return move_string;
+    }
+
+    snprintf (move_string, LOA_MOVE_STRING_SIZE,
+             "%c%d%c%c%d",
+             'a' + move->x1, LOA_BOARD_SIZE - move->y1,
+             move->is_capture ? 'x' : '-',
+             'a' + move->x2, LOA_BOARD_SIZE - move->y2);
+
+    return move_string;
+}
+
+loa_bool_t
+loa_move_init_from_string (loa_move_t *move, const char *string)
+{
+    char xc1, xc2, sep;
+    int x1, y1, x2, y2;
+    int matched;
+
+    /* Avoid returning uninitialized data on error. */
+    move->x1 = 0;
+    move->y1 = 0;
+    move->x2 = 0;
+    move->y2 = 0;
+    move->is_capture = 0;
+
+    matched = sscanf (string, "%c%d%c%c%d", &xc1, &y1, &sep, &xc2, &y2);
+    if (matched != 5)
+       return FALSE;
+
+    x1 = tolower (xc1) - 'a';
+    x2 = tolower (xc2) - 'a';
+    y1 = LOA_BOARD_SIZE - y1;
+    y2 = LOA_BOARD_SIZE - y2;
+
+    if (x1 < 0 || x1 >= LOA_BOARD_SIZE ||
+       y1 < 0 || y1 >= LOA_BOARD_SIZE ||
+       x2 < 0 || x2 >= LOA_BOARD_SIZE ||
+       y2 < 0 || y2 >= LOA_BOARD_SIZE)
+    {
+       return FALSE;
+    }
+
+    if (sep != '-' && sep != 'x' && sep != 'X')
+       return FALSE;
+
+    move->x1 = x1;
+    move->y1 = y1;
+    move->x2 = x2;
+    move->y2 = y2;
+
+    if (sep == 'x' || sep == 'X')
+       move->is_capture = TRUE;
+    else
+       move->is_capture = FALSE;
+
+    return TRUE;
+}
+
 /* Given an (x,y) position on the board, return the index of the array
  * used to count pieces in diagonal rows running from upper-left to
  * lower-right, (like a grave accent: à or like a backslash: \).
@@ -89,6 +168,22 @@ loa_board_remove_piece (loa_board_t *board, int x, int y)
 
 void
 loa_board_init (loa_board_t *board)
+{
+    board->moves = NULL;
+    board->moves_size = 0;
+
+    loa_board_reset (board);
+}
+
+void
+loa_board_fini (loa_board_t *board)
+{
+    if (board->moves)
+       free (board->moves);
+}
+
+void
+loa_board_reset (loa_board_t *board)
 {
     int i, x, y;
 
@@ -116,6 +211,9 @@ loa_board_init (loa_board_t *board)
        loa_board_add_piece (board, LOA_BOARD_SIZE - 1, i, LOA_CELL_WHITE);
     }
 
+    /* Leave board->moves and board->moves_size as allocated */
+    board->num_moves = 0;
+
     board->player = LOA_PLAYER_BLACK;
 }
 
@@ -177,22 +275,25 @@ loa_board_is_won (loa_board_t *board, int x, int y)
 }
 
 static loa_bool_t
-loa_board_move_legal (loa_board_t *board,
-                     int x1, int y1,
-                     int x2, int y2,
-                     char **error)
+loa_board_move_legal (loa_board_t       *board,
+                     const loa_move_t   *move,
+                     char              **error)
 {
     int x, y;
+    int x1, y1, x2, y2;
     int dx, dy;
     int step_x, step_y;
 
-    if (x1 < 0 || y1 < 0 || x1 >= LOA_BOARD_SIZE || y1 >= LOA_BOARD_SIZE ||
-       x2 < 0 || y2 < 0 || x2 >= LOA_BOARD_SIZE || y2 >= LOA_BOARD_SIZE)
+    if (! loa_move_is_valid (move))
     {
        *error = "Invalid coordinates (not on board)";
        return FALSE;
     }
 
+    x1 = move->x1;
+    y1 = move->y1;
+    x2 = move->x2;
+    y2 = move->y2;
 
     if (board->cells[x1][y1] == LOA_CELL_EMPTY) {
        *error = "There is no piece there to move";
@@ -285,6 +386,31 @@ loa_board_pass (loa_board_t *board)
     return TRUE;
 }
 
+/* Once the move is validated and executed, append it to the moves
+ * array that stores the move history. */
+static void
+loa_board_add_move_to_history (loa_board_t     *board,
+                              const loa_move_t *move)
+{
+    board->num_moves++;
+
+    if (board->num_moves > board->moves_size) {
+       if (board->moves_size)
+           board->moves_size *= 2;
+       else
+           board->moves_size = 20;
+
+       board->moves = realloc (board->moves,
+                               board->moves_size * sizeof (loa_move_t));
+       if (board->moves == NULL) {
+           fprintf (stderr, "Out of memory.\n");
+           exit (1);
+       }
+    }
+
+    board->moves[board->num_moves - 1] = *move;
+}
+
 int
 loa_board_move (loa_board_t *board,
                int x1, int y1,
@@ -292,13 +418,30 @@ loa_board_move (loa_board_t *board,
                char **error)
 {
     loa_cell_t cell;
+    loa_move_t move;
 
-    if (! loa_board_move_legal (board, x1, y1, x2, y2, error))
+    move.x1 = x1;
+    move.y1 = y1;
+    move.x2 = x2;
+    move.y2 = y2;
+
+    if (! loa_board_move_legal (board, &move, error))
        return FALSE;
 
     cell = loa_board_remove_piece (board, x1, y1);
-    loa_board_remove_piece (board, x2, y2);
-    loa_board_add_piece (board, x2, y2, cell);
+    assert (cell == board->player);
+
+    cell = loa_board_remove_piece (board, x2, y2);
+    if (cell == LOA_CELL_EMPTY) {
+       move.is_capture = FALSE;
+    } else {
+       assert (cell != board->player);
+       move.is_capture = TRUE;
+    }
+
+    loa_board_add_piece (board, x2, y2, board->player);
+
+    loa_board_add_move_to_history (board, &move);
 
     loa_board_next_player (board);
 
index 4532e01f156c2d22c67ce7549e3c3f3d437cd517..4a4f690855fdd1541d365c29af262e6b5c0acfab 100644 (file)
@@ -40,6 +40,25 @@ typedef enum {
     LOA_CELL_EMPTY
 } loa_cell_t;
 
+typedef struct {
+    int x1; int y1;
+    int x2; int y2;
+    loa_bool_t is_capture;
+} loa_move_t;
+
+/* Return a string representation of a move. The return value is a
+ * pointer to a static buffer that will be reused from one call to the
+ * next. So the contents should be copied if needed. This function
+ * call is not thread-safe. */
+const char *
+loa_move_to_string (const loa_move_t *move);
+
+/* Initialize an loa_move_t structure based on a string value,
+ * (presumably the result of loa_move_to_string). Returns TRUE if
+ * successful. */
+loa_bool_t
+loa_move_init_from_string (loa_move_t *move, const char *string);
+
 /* The implementation of board_group_size depends on the square of
  * BOARD_SIZE being less than or equal to 64. */
 #define LOA_BOARD_SIZE 8
@@ -58,14 +77,29 @@ typedef struct {
     int diag_grave_pieces[LOA_DIAG_ARRAY_SIZE];
     int diag_acute_pieces[LOA_DIAG_ARRAY_SIZE];
 
+    int num_moves;
+    loa_move_t *moves;
+    int moves_size;
+
     loa_player_t player;
 } loa_board_t;
 
-/* Initialize a board for a new game of Lines of Action. The 12 pieces
+/* Initialize an loa_board_t structure. This function must be called
+ * before passing the board to any other loa_board function. It will
+ * implicitly call loa_board_reset for you. When you are finished
+ * using the board, you should call loa_board_fini. */
+void
+loa_board_init (loa_board_t *board);
+
+/* Free all resources associated with a board. */
+void
+loa_board_fini (loa_board_t *board);
+
+/* Reset board for a new game of Lines of Action. The 12 pieces
  * for black and white will be put in their initial places and black
  * will be set as the current player. */
 void
-loa_board_init (loa_board_t *board);
+loa_board_reset (loa_board_t *board);
 
 /* Does the square at (x,y) belong to a winning group? That is, is
  * there a piece at (x,y) that is 8-way connected to all pieces on the