Add support for a 'history' command
[loudgame] / lg-loa.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 "loudgame.h"
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdint.h>
25
26 #include <string.h>
27 #include <ctype.h>
28 #include <assert.h>
29
30 #include "loa-board.h"
31
32 typedef struct _loa_game {
33     loudgame_t lg;
34     loa_board_t board;
35 } loa_game_t;
36
37 static void
38 loa_game_new_game (loa_game_t *game)
39 {
40     loa_board_reset (&game->board);
41 }
42
43 static loa_bool_t
44 loa_game_move (loa_game_t *game, const char * peer,
45                int x1, int y1, int x2, int y2)
46 {
47     char *error;
48
49     if (! loa_board_move (&game->board, x1, y1, x2, y2, &error)) {
50         loudgame_sendf (&game->lg, peer, "Illegal move: %c%d%c%d: %s",
51                         'a' + x1, LOA_BOARD_SIZE - y1,
52                         'a' + x2, LOA_BOARD_SIZE - y2,
53                         error);
54         return FALSE;
55     }
56
57     return TRUE;
58 }
59
60 static void
61 loa_game_handle_show (loa_game_t *game,
62                       const char *peer)
63 {
64     LmMessage *reply;
65     LmMessageNode *html, *body, *span;
66     gboolean result;
67     GError *error = NULL;
68     char *board_string;
69     char *line, *newline;
70
71     reply = lm_message_new (peer, LM_MESSAGE_TYPE_MESSAGE);
72     html = lm_message_node_add_child (reply->node, "html", "");
73     lm_message_node_set_attribute (html,
74                                    "xmlns",
75                                    "http://jabber.org/protocol/xhtml-im");
76     body = lm_message_node_add_child (html, "body", "");
77     lm_message_node_set_attribute (body,
78                                    "xmlns",
79                                    "http://www.w3.org/1999/xhtml");
80
81     if (game->board.player == LOA_PLAYER_BLACK)
82         lm_message_node_add_child (body, "span", "Black to move:");
83     else
84         lm_message_node_add_child (body, "span", "White to move:");
85
86     board_string = loa_board_to_string (&game->board);
87
88     line = board_string;
89     while (1) {
90         newline = strchr (line, '\n');
91         if (newline)
92             *newline = '\0';
93         lm_message_node_add_child (body, "br", "");
94         span = lm_message_node_add_child (body, "span", line);
95         lm_message_node_set_attribute (span, "style",
96                                        "font-family: Monospace;");
97         if (! newline)
98             break;
99         line = newline + 1;
100     }
101
102     free (board_string);
103
104     result = lm_connection_send (game->lg.connection, reply, &error);
105     lm_message_unref (reply);
106
107     if (! result) {
108         g_error ("lm_connection_send failed: %s\n",
109                  error->message);
110         loudgame_quit (&game->lg, 1);
111     }
112 }
113
114 static void
115 loa_game_handle_move (loa_game_t *game,
116                       const char *peer,
117                       const char *move)
118 {
119     char xc1, xc2;
120     int x1, y1, x2, y2;
121     int matched;
122
123     matched = sscanf (move, " %c %d %c %d ", &xc1, &y1, &xc2, &y2);
124     if (matched != 4) {
125         loudgame_sendf (&game->lg, peer,
126                         "Error: The 'move' command requires a move of the form 'b1d3'");
127         return;
128     }
129
130     x1 = tolower (xc1) - 'a';
131     x2 = tolower (xc2) - 'a';
132     /* We use an upper-left origin internally. */
133     y1 = LOA_BOARD_SIZE - y1;
134     y2 = LOA_BOARD_SIZE - y2;
135     if (! loa_game_move (game, peer, x1, y1, x2, y2))
136         return;
137
138     loudgame_broadcastf (&game->lg, "%c%d%c%d",
139                          'a' + x1, LOA_BOARD_SIZE - y1,
140                          'a' + x2, LOA_BOARD_SIZE - y2);
141
142     if (loa_board_is_won (&game->board, x2, y2))
143         loudgame_broadcastf (&game->lg, "%s wins", peer);
144 }
145
146 static void
147 loa_game_handle_history (loa_game_t *game,
148                          const char *peer)
149 {
150     int i;
151
152     for (i = 0; i < game->board.num_moves; i++)
153         loudgame_sendf (&game->lg, peer, "%s",
154                         loa_move_to_string (&game->board.moves[i]));
155 }
156
157 static void
158 loa_game_handle_pass (loa_game_t *game, const char *peer)
159 {
160     loudgame_broadcastf (&game->lg, "%s passes", peer);
161
162     if (loa_board_pass (&game->board))
163         return;
164
165     loudgame_sendf (&game->lg, peer,
166                     "Error: You cannot pass since you have a legal move available");
167 }
168
169 static void
170 loa_game_handle_help (loa_game_t *game, const char *peer)
171 {
172     loudgame_sendf (&game->lg, peer,
173                     "I'm a bot that allows you to play the game Lines of Action.\n"
174                     "Here are some generic commands I understand:\n"
175                     LOUDGAME_HELP
176                     "\n"
177                     "And some game-specific commands:\n"
178                     "\tshow     \t\tShow the current board\n"
179                     "\tmove aNbN\tMove a piece, (eg. 'move b1d3')\n"
180                     "\tpass     \t\tSkip a turn (only legal if no moves are possible)\n"
181                     "\tnew      \t\tBegin a new game\n"
182                     "\thistory  \t\tShow the move history of the game\n"
183                     "\thelp     \t\tThis help message\n"
184                     "\trules    \t\tA description of the Lines of Action rules\n"
185                     "\n"
186                     "Lines of Action was invented by Claude Soucie and first made popular\n"
187                     "when its rules were published by Sid Sackson in \"A Gamut of Games\" (1969).\n"
188                     "\n"
189                     "If you are new to Lines of Action, type 'rules' now to learn the rules.\n");
190 }
191
192 static void
193 loa_game_handle_rules (loa_game_t *game, const char *peer)
194 {
195     loudgame_sendf (&game->lg, peer,
196                     "Lines of Action can be played with a standard (English) checkers set,\n"
197                     "that is an 8x8 board and 12 markers each of contrasting colors ('black' and\n"
198                     "'white'). The initial placement has 6 each of the black pieces on the top\n"
199                     "and bottom rows, and 6 each of the white pieces on the left and right columns\n"
200                     "leaving the four corner spaces empty. Play begins with the black player and\n"
201                     "then alternates.\n"
202                     "\n"
203                     "On each move a piece is moved in a straight line in any of eight directions,\n"
204                     "(similar to a queen's move in chess), but must be moved exactly the same\n"
205                     "number of spaces as there are pieces (of either color) in the row, column,\n"
206                     "or diagonal of the move. A piece may jump over pieces of its own color, but\n"
207                     "may not jump a piece of the opposite color. The final square of the move can\n"
208                     "be either empty or can contain a piece of the opposing color, in which case\n"
209                     "that piece is removed from the game.\n"
210                     "\n"
211                     "If a player has no possible move, then that player must pass, (but if the\n"
212                     "player has a possible move, then the player cannot pass).\n"
213                     "\n"
214                     "The goal of the game is to connect all of your remaining pieces into\n"
215                     "a single, connected group. Pieces that are diagonally adjacent are\n"
216                     "considered connected.\n"
217                     "\n"
218                     "If a move simultaneously creates a winning condition for both players, this\n"
219                     "is considered a win for the player making the move.\n"
220                     "\n"
221                     "Notes on this implementation:\n"
222                     "\n"
223                     "This implementation enforces the move rules described above, but will\n"
224                     "allow any person to make moves, (that is, you can control both\n"
225                     "colors). Think of it as being somewhat like a physical board where\n"
226                     "any person could move the pieces at any time, but generally two people\n"
227                     "will take turns and others will politely watch.\n"
228                     "\n"
229                     "Also, the game won't currently show you the board again after a\n"
230                     "player makes a move, so you'll need to keep typing 'show' to see\n"
231                     "updates. Yes, this is a bug, and yes it will be fixed soon.\n"
232                     "\n"
233                     "Lines of Action was invented by Claude Soucie and first made popular\n"
234                     "when its rules were published by Sid Sackson in \"A Gamut of Games\" (1969).\n");
235 }
236
237 static void
238 loa_game_handle_new (loa_game_t *game, const char *peer)
239 {
240     loudgame_broadcastf (&game->lg, "%s has started a new game", peer);
241     loa_game_new_game (game);
242 }
243
244 static void
245 loa_game_handle_message (loudgame_t *lg,
246                          const char *peer,
247                          const char *message)
248 {
249     loa_game_t *game = (loa_game_t *) lg;
250
251     if (strcmp (message, "show") == 0)
252         loa_game_handle_show (game, peer);
253     else if (strncmp (message, "move", 4) == 0)
254         loa_game_handle_move (game, peer, message + 4);
255     else if (strcmp (message, "pass") == 0)
256         loa_game_handle_pass (game, peer);
257     else if (strcmp (message, "new") == 0)
258         loa_game_handle_new (game, peer);
259     else if (strcmp (message, "history") == 0)
260         loa_game_handle_history (game, peer);
261     else if (strcmp (message, "help") == 0)
262         loa_game_handle_help (game, peer);
263     else if (strcmp (message, "rules") == 0)
264         loa_game_handle_rules (game, peer);
265     else
266         loudgame_sendf (lg, peer, "Unknown command: '%s'. Use 'help' to get a list of valid commands.", message);
267 }
268
269 static int
270 loa_game_init (loa_game_t *game, int argc, char *argv[])
271 {
272     int err;
273
274     err = loudgame_init (&game->lg, argc, argv);
275     if (err)
276         return err;
277
278     loa_board_init (&game->board);
279
280     loa_game_new_game (game);
281
282     return 0;
283 }
284
285 int
286 main (int argc, char **argv)
287 {
288     loa_game_t game;
289     int err;
290
291     err = loa_game_init (&game, argc, argv);
292     if (err)
293         return err;
294
295     game.lg.handle_message = loa_game_handle_message;
296
297     err = loudgame_run (&game.lg);
298     if (err)
299         return err;
300
301     return 0;
302 }