Add a fancy HTML-based board display so it's possible to play with pidgin, say.
[loudgame] / lg-set.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 <string.h>
25 #include <unistd.h>
26 #include <math.h>
27 #include <time.h>
28 #include <ctype.h>
29
30 #include <assert.h>
31
32 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
33
34 #define NUM_ATTRIBUTES 4
35 #define NUM_VALUES 3
36
37 char *attribute_names[NUM_ATTRIBUTES] = {
38     "number", "color", "shading", "symbol"
39 };
40
41 typedef enum { ATTRIBUTE_INDEX_NUMBER,
42                ATTRIBUTE_INDEX_COLOR,
43                ATTRIBUTE_INDEX_SHADING,
44                ATTRIBUTE_INDEX_SYMBOL } attribute_index_t;
45
46 char *attribute_values[NUM_ATTRIBUTES][NUM_VALUES] = {
47     { "1", "2", "3" },
48     { "red", "green", "purple" },
49     { "solid", "open", "striped" },
50     { "oval", "squiggle", "diamond" }
51 };
52
53 typedef enum { COLOR_RED, COLOR_GREEN, COLOR_PURPLE } color_t;
54 typedef enum { SHADING_OPEN, SHADING_STRIPED, SHADING_SOLID } shading_t;
55 typedef enum { SYMBOL_OVAL, SYMBOL_SQUIGGLE, SYMBOL_DIAMOND } symbol_t;
56
57 typedef struct card {
58     int attributes[NUM_ATTRIBUTES];
59 } card_t;
60
61 #define DECK_MAX_CARDS ((int) (pow (NUM_VALUES, NUM_ATTRIBUTES)))
62 typedef struct deck {
63     int num_cards;
64     card_t cards[DECK_MAX_CARDS];
65 } deck_t;
66
67 typedef struct slot {
68     int has_card;
69     card_t card;
70     int selected;
71 } slot_t;
72
73 #define BOARD_COLS 3
74 #define BOARD_ROWS 4
75 #define BOARD_MAX_SLOTS (BOARD_COLS * BOARD_ROWS)
76 typedef struct board {
77     int num_slots;
78     slot_t slots[BOARD_MAX_SLOTS];
79     int sets_possible;
80 } board_t;
81
82 typedef struct _set_game {
83     loudgame_t lg;
84     deck_t deck;
85     board_t board;
86 } set_game_t;
87
88 static void
89 board_count_sets_possible (board_t *board);
90
91 static int
92 set_game_shuffle (set_game_t *game);
93
94 static void
95 set_game_new_game (set_game_t *game);
96
97 static int
98 attribute_all_same (card_t *cards, int num_cards, int attr)
99 {
100     int i, value;
101
102     if (num_cards == 0)
103         return 1;
104
105     value = cards[0].attributes[attr];
106
107     for (i = 1; i < num_cards; i++)
108         if (cards[i].attributes[attr] != value)
109             return 0;
110
111     return 1;
112 }
113
114 static int
115 attribute_all_different (card_t *cards, int num_cards, int attr)
116 {
117     int i, seen[NUM_VALUES];
118
119     if (num_cards == 0)
120         return 1;
121
122     memset (seen, 0, sizeof (seen));
123
124     for (i = 0; i < num_cards; i++) {
125         if (seen[cards[i].attributes[attr]])
126             return 0;
127         seen[cards[i].attributes[attr]] = 1;
128     }
129
130     return 1;
131 }
132
133 static int
134 is_set (card_t *cards, int num_cards)
135 {
136     int attr;
137
138     for (attr = 0; attr < NUM_ATTRIBUTES; attr++)
139         if (! attribute_all_same (cards, num_cards, attr) &&
140             ! attribute_all_different (cards, num_cards, attr))
141         {
142             return 0;
143         }
144
145     return 1;
146 }
147
148 static void
149 deck_shuffle (deck_t *deck)
150 {
151     int i, r;
152     card_t tmp;
153
154     assert (deck->num_cards <= DECK_MAX_CARDS);
155
156     for (i=deck->num_cards - 1; i>=0; i--) {
157         r = (int) i * (rand() / (RAND_MAX + 1.0));
158         assert (r >= 0);
159         assert (r <= i);
160         tmp = deck->cards[i];
161         deck->cards[i] = deck->cards[r];
162         deck->cards[r] = tmp;
163     }
164 }
165
166 static void
167 deck_init (deck_t *deck)
168 {
169     int card;
170     int number;
171     color_t color;
172     shading_t shading;
173     symbol_t symbol;
174
175     card = 0;
176     for (number = 0; number < NUM_VALUES; number++)
177         for (color = 0; color < NUM_VALUES; color++)
178             for (shading = 0; shading < NUM_VALUES; shading++)
179                 for (symbol = 0; symbol < NUM_VALUES; symbol++) {
180                     deck->cards[card].attributes[ATTRIBUTE_INDEX_NUMBER] = number;
181                     deck->cards[card].attributes[ATTRIBUTE_INDEX_COLOR] = color;
182                     deck->cards[card].attributes[ATTRIBUTE_INDEX_SHADING] = shading;
183                     deck->cards[card].attributes[ATTRIBUTE_INDEX_SYMBOL] = symbol;
184                     card++;
185                 }
186     deck->num_cards = card;
187
188     deck_shuffle (deck);
189 }
190
191 static void
192 board_count_sets_possible (board_t *board)
193 {
194     int i, j, k;
195     int sets_possible = 0;
196     card_t cards[3];
197
198     for (i = 0; i < board->num_slots; i++) {
199         if (! board->slots[i].has_card)
200             continue;
201         for (j = i+1; j < board->num_slots; j++) {
202             if (! board->slots[j].has_card)
203                 continue;
204             for (k = j+1; k < board->num_slots; k++) {
205                 if (! board->slots[k].has_card)
206                     continue;
207                 cards[0] = board->slots[i].card;
208                 cards[1] = board->slots[j].card;
209                 cards[2] = board->slots[k].card;
210                 if (is_set (cards, 3))
211                     sets_possible++;
212             }
213         }
214     }
215
216     board->sets_possible = sets_possible;
217 }
218
219 static void
220 board_init (board_t *board)
221 {
222     int i;
223     board->num_slots = BOARD_MAX_SLOTS;
224     for (i=0; i < board->num_slots; i++) {
225         board->slots[i].has_card = 0;
226         board->slots[i].selected = 0;
227     }
228
229     board_count_sets_possible (board);
230 }
231
232 static void
233 deal (deck_t *deck, board_t *board)
234 {
235     int i;
236
237     for (i=0; i < board->num_slots; i++)
238         if (! board->slots[i].has_card) {
239             if (deck->num_cards > 0) {
240                 board->slots[i].card = deck->cards[deck->num_cards-- -1];
241                 board->slots[i].has_card = 1;
242             }
243         }
244
245     board_count_sets_possible (board);
246 }
247
248 static void
249 set_game_handle_show (set_game_t        *game,
250                       const char        *peer)
251 {
252     LmMessage *reply;
253     LmMessageNode *html, *body, *span;
254     gboolean result;
255     GError *error = NULL;
256     board_t *board = &game->board;
257     char board_str[BOARD_MAX_SLOTS * (NUM_ATTRIBUTES + 1)];
258     char *s;
259     int slot, pos, attr;
260
261     reply = lm_message_new (peer, LM_MESSAGE_TYPE_MESSAGE);
262     html = lm_message_node_add_child (reply->node, "html", "");
263     lm_message_node_set_attribute (html,
264                                    "xmlns",
265                                    "http://jabber.org/protocol/xhtml-im");
266     body = lm_message_node_add_child (html, "body", "");
267     lm_message_node_set_attribute (body,
268                                    "xmlns",
269                                    "http://www.w3.org/1999/xhtml");
270
271     s = board_str;
272     for (slot = 0; slot < board->num_slots; slot++) {
273         if (slot % 4 == 0)
274             lm_message_node_add_child (body, "br", "");
275         if (board->slots[slot].has_card) {
276             char card_str[5];
277             char *style;
278             card_t card = board->slots[slot].card;
279             for (pos = 0; pos < 3; pos++) {
280                 if (pos <= card.attributes[ATTRIBUTE_INDEX_NUMBER]) {
281                     switch (card.attributes[ATTRIBUTE_INDEX_SYMBOL]) {
282                     case SYMBOL_OVAL:
283                         card_str[pos] = 'O';
284                         break;
285                     case SYMBOL_SQUIGGLE:
286                         card_str[pos] = 'S';
287                         break;
288                     case SYMBOL_DIAMOND:
289                     default:
290                         card_str[pos] = 'X';
291                         break;
292                     }
293                 } else {
294                     card_str[pos] = ' ';
295                 }
296             }
297             card_str[3] = ' ';
298             card_str[4] = '\0';
299             span = lm_message_node_add_child (body, "span",
300                                               card_str);
301             style = g_strdup_printf ("font-family: Monospace; color: %s;%s%s",
302                                      attribute_values[ATTRIBUTE_INDEX_COLOR][
303                                          card.attributes[ATTRIBUTE_INDEX_COLOR]],
304                                      card.attributes[ATTRIBUTE_INDEX_SHADING]
305                                      == SHADING_SOLID ? "font-weight: bold;" : "",
306                                      card.attributes[ATTRIBUTE_INDEX_SHADING]
307                                      == SHADING_STRIPED ? "text-decoration: underline;" : "");
308             lm_message_node_set_attribute (span, "style", style);
309             free (style);
310             for (attr = 0; attr < NUM_ATTRIBUTES; attr++)
311                 *s++ = '0' + board->slots[slot].card.attributes[attr];
312             *s++ = ' ';
313         } else {
314             span = lm_message_node_add_child (body, "span", "    ");
315             lm_message_node_set_attribute (span, "style",
316                                            "font-family: Monospace;");
317             *s++ = '-';
318             *s++ = '-';
319             *s++ = '-';
320             *s++ = ' ';
321         }
322     }
323     *s = '\0';
324
325     lm_message_node_add_child (reply->node, "body", board_str);
326
327     result = lm_connection_send (game->lg.connection, reply, &error);
328     lm_message_unref (reply);
329
330     if (! result) {
331         g_error ("lm_connection_send failed: %s\n",
332                  error->message);
333         loudgame_quit (&game->lg, 1);
334     }
335 }
336
337 static void
338 set_game_handle_hint (set_game_t *game,
339                       const char *peer)
340 {
341     loudgame_sendf (&game->lg, peer, "Sets possible: %d",
342                     game->board.sets_possible);
343 }
344
345 static void
346 set_game_handle_shuffle (set_game_t *game,
347                          const char *peer)
348 {
349     if (game->board.sets_possible) {
350         loudgame_sendf (&game->lg, peer,
351                         "There are %d sets, refusing to shuffle.",
352                         game->board.sets_possible);
353         return;
354     }
355
356     if (game->deck.num_cards == 0) {
357         loudgame_sendf (&game->lg, peer,
358                         "Deck exhausted, starting a new game.");
359         set_game_new_game (game);
360     } else {
361         set_game_shuffle (game);
362     }
363 }
364
365 static void
366 set_game_handle_set (set_game_t *game,
367                      const char *peer,
368                      const char *message)
369 {
370     const char *s;
371     char *end = NULL;
372     int i;
373     int slots[3];
374     card_t cards[3];
375
376     s = message;
377     i = 0;
378     while (*s && i < 3) {
379         slots[i++] = strtoul (s, &end, 10);
380         if (end == s) {
381             loudgame_sendf (&game->lg, peer,
382                             "Error: Not an integer: %s", s);
383             return;
384         }
385         s = end;
386     }
387
388     while (end && *end && isspace (*end))
389         end++;
390
391     if (i != 3 || *end != '\0') {
392         loudgame_sendf (&game->lg, peer,
393                         "Error: The 'set' command requires exactly 3 integers");
394         return;
395     }
396
397     for (i = 0; i < 3; i++) {
398         if (slots[i] < 0 || slots[i] > BOARD_MAX_SLOTS) {
399             loudgame_sendf (&game->lg, peer,
400                             "Error: Value %d is out of range (0-%d)",
401                             slots[i], BOARD_MAX_SLOTS);
402             return;
403         }
404     }
405
406     if (slots[0] == slots[1] ||
407         slots[0] == slots[2] ||
408         slots[1] == slots[2])
409     {
410             loudgame_sendf (&game->lg, peer,
411                             "Error: All 3 values must be unique");
412             return;
413     }
414
415     for (i=0; i < 3; i++) {
416         if (game->board.slots[slots[i]].has_card) {
417             cards[i] = game->board.slots[slots[i]].card;
418         } else {
419             loudgame_sendf (&game->lg, peer,
420                             "Error: There's no card at position %d", i);
421             return;
422         }
423     }
424
425     if (! is_set (cards, 3)) {
426         loudgame_sendf (&game->lg, peer,
427                         "Sorry, that's not a set");
428         return;
429     }
430
431     loudgame_sendf (&game->lg, peer,
432                     "Yes, that's a set!");
433
434     for (i = 0; i < 3; i++)
435         game->board.slots[slots[i]].has_card = 0;
436
437     board_count_sets_possible (&game->board);
438
439     deal (&game->deck, &game->board);
440 }
441
442 static void
443 set_game_handle_message (loudgame_t *lg,
444                          const char *peer,
445                          const char *message)
446 {
447     set_game_t *game = (set_game_t*) lg;
448
449     if (strcmp (message, "show") == 0)
450         set_game_handle_show (game, peer);
451     else if (strcmp (message, "hint") == 0)
452         set_game_handle_hint (game, peer);
453     else if (strcmp (message, "shuffle") == 0)
454         set_game_handle_shuffle (game, peer);
455     else if (strncmp (message, "set", 3) == 0)
456         set_game_handle_set (game, peer, message + 3);
457     else
458         loudgame_sendf (lg, peer, "Unknown command: '%s'", message);
459 }
460
461 /* Begin a new game */
462 static void
463 set_game_new_game (set_game_t *game)
464 {
465     deck_init (&game->deck);
466     board_init (&game->board);
467     deal (&game->deck, &game->board);
468 }
469
470 static int
471 set_game_init (set_game_t *game, int argc, char *argv[])
472 {
473     int err;
474
475     err = loudgame_init (&game->lg, argc, argv);
476     if (err)
477         return err;
478
479     set_game_new_game (game);
480
481     return 0;
482 }
483
484 /* Return the dealt cards to the deck, reshuffle, and deal again. */
485 static int
486 set_game_shuffle (set_game_t *game)
487 {
488     board_t *board = &game->board;
489     deck_t *deck = &game->deck;
490     int i;
491
492     for (i=0; i < board->num_slots; i++) {
493         if (board->slots[i].has_card) {
494             deck->cards[++deck->num_cards - 1] = board->slots[i].card;
495             board->slots[i].has_card = 0;
496             board->slots[i].selected = 0;
497         }
498     }
499
500     deck_shuffle (deck);
501     deal (deck, board);
502
503     return 1;
504 }
505
506 int
507 main (int argc, char *argv[])
508 {
509     set_game_t game;
510     int err;
511
512     srand (time(0));
513
514     err = set_game_init (&game, argc, argv);
515     if (err)
516         return err;
517
518     game.lg.handle_message = set_game_handle_message;
519
520     err = loudgame_run (&game.lg);
521     if (err)
522         return err;
523
524     return 0;
525 }