]> git.cworth.org Git - loudgame/blob - lg-set.c
Change resource from TestLM to loudgame
[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     loudgame_broadcastf (&game->lg,
434                          "%s found the set: %d %d %d. Please send a new 'show' command.",
435                          peer,
436                          slots[0], slots[1], slots[2]);
437
438     for (i = 0; i < 3; i++)
439         game->board.slots[slots[i]].has_card = 0;
440
441     board_count_sets_possible (&game->board);
442
443     deal (&game->deck, &game->board);
444 }
445
446 static void
447 set_game_handle_help (set_game_t *game,
448                       const char *peer)
449 {
450     loudgame_sendf (&game->lg, peer,
451                     "I'm a bot that allows you to play the game of SET.\n"
452                     "Here are some generic commands I understand:\n"
453                     LOUDGAME_HELP
454                     "\n"
455                     "And some game-specific commands:\n"
456                     "\tshow     \tShow the current cards on the board\n"
457                     "\thint     \tIndicate how many sets are currently possible\n"
458                     "\tshuffle  \tReturn the cards to the deck, shuffle and deal\n"
459                     "\t         \t(this is only allowed if no sets are possible)\n"
460                     "\tset X Y Z\tClaim three cards as a set. The cards are numbered\n"
461                     "\t         \tleft-to-right, and top-to-bottom from 0 to 11.\n"
462                     "\n"
463                     "For more information about SET, or to purchase your own deck, visit\n"
464                     "http://setgame.com . Please note that this server is unaffiliated\n"
465                     "with Set Enterprises, Inc., who publish the SET card game.");
466 }
467
468 static void
469 set_game_handle_message (loudgame_t *lg,
470                          const char *peer,
471                          const char *message)
472 {
473     set_game_t *game = (set_game_t*) lg;
474
475     if (strcmp (message, "show") == 0)
476         set_game_handle_show (game, peer);
477     else if (strcmp (message, "help") == 0)
478         set_game_handle_help (game, peer);
479     else if (strcmp (message, "hint") == 0)
480         set_game_handle_hint (game, peer);
481     else if (strcmp (message, "shuffle") == 0)
482         set_game_handle_shuffle (game, peer);
483     else if (strncmp (message, "set", 3) == 0)
484         set_game_handle_set (game, peer, message + 3);
485     else
486         loudgame_sendf (lg, peer, "Unknown command: '%s'. Use 'help' to get a list of valid commands.", message);
487 }
488
489 /* Begin a new game */
490 static void
491 set_game_new_game (set_game_t *game)
492 {
493     deck_init (&game->deck);
494     board_init (&game->board);
495     deal (&game->deck, &game->board);
496 }
497
498 static int
499 set_game_init (set_game_t *game, int argc, char *argv[])
500 {
501     int err;
502
503     err = loudgame_init (&game->lg, argc, argv);
504     if (err)
505         return err;
506
507     set_game_new_game (game);
508
509     return 0;
510 }
511
512 /* Return the dealt cards to the deck, reshuffle, and deal again. */
513 static int
514 set_game_shuffle (set_game_t *game)
515 {
516     board_t *board = &game->board;
517     deck_t *deck = &game->deck;
518     int i;
519
520     for (i=0; i < board->num_slots; i++) {
521         if (board->slots[i].has_card) {
522             deck->cards[++deck->num_cards - 1] = board->slots[i].card;
523             board->slots[i].has_card = 0;
524             board->slots[i].selected = 0;
525         }
526     }
527
528     deck_shuffle (deck);
529     deal (deck, board);
530
531     return 1;
532 }
533
534 int
535 main (int argc, char *argv[])
536 {
537     set_game_t game;
538     int err;
539
540     srand (time(0));
541
542     err = set_game_init (&game, argc, argv);
543     if (err)
544         return err;
545
546     game.lg.handle_message = set_game_handle_message;
547
548     err = loudgame_run (&game.lg);
549     if (err)
550         return err;
551
552     return 0;
553 }