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