2 * Copyright (C) 2008 Carl Worth
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.
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.
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/ .
17 * Author: Carl Worth <cworth@cworth.org>
32 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
34 #define NUM_ATTRIBUTES 4
37 char *attribute_names[NUM_ATTRIBUTES] = {
38 "number", "color", "shading", "symbol"
41 typedef enum { ATTRIBUTE_INDEX_NUMBER,
42 ATTRIBUTE_INDEX_COLOR,
43 ATTRIBUTE_INDEX_SHADING,
44 ATTRIBUTE_INDEX_SYMBOL } attribute_index_t;
46 char *attribute_values[NUM_ATTRIBUTES][NUM_VALUES] = {
48 { "red", "green", "purple" },
49 { "solid", "open", "striped" },
50 { "oval", "squiggle", "diamond" }
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;
58 int attributes[NUM_ATTRIBUTES];
61 #define DECK_MAX_CARDS ((int) (pow (NUM_VALUES, NUM_ATTRIBUTES)))
64 card_t cards[DECK_MAX_CARDS];
75 #define BOARD_MAX_SLOTS (BOARD_COLS * BOARD_ROWS)
76 typedef struct board {
78 slot_t slots[BOARD_MAX_SLOTS];
82 typedef struct _set_game {
89 board_count_sets_possible (board_t *board);
92 set_game_shuffle (set_game_t *game);
95 set_game_new_game (set_game_t *game);
98 attribute_all_same (card_t *cards, int num_cards, int attr)
105 value = cards[0].attributes[attr];
107 for (i = 1; i < num_cards; i++)
108 if (cards[i].attributes[attr] != value)
115 attribute_all_different (card_t *cards, int num_cards, int attr)
117 int i, seen[NUM_VALUES];
122 memset (seen, 0, sizeof (seen));
124 for (i = 0; i < num_cards; i++) {
125 if (seen[cards[i].attributes[attr]])
127 seen[cards[i].attributes[attr]] = 1;
134 is_set (card_t *cards, int num_cards)
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))
149 deck_shuffle (deck_t *deck)
154 assert (deck->num_cards <= DECK_MAX_CARDS);
156 for (i=deck->num_cards - 1; i>=0; i--) {
157 r = (int) i * (rand() / (RAND_MAX + 1.0));
160 tmp = deck->cards[i];
161 deck->cards[i] = deck->cards[r];
162 deck->cards[r] = tmp;
167 deck_init (deck_t *deck)
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;
186 deck->num_cards = card;
192 board_count_sets_possible (board_t *board)
195 int sets_possible = 0;
198 for (i = 0; i < board->num_slots; i++) {
199 if (! board->slots[i].has_card)
201 for (j = i+1; j < board->num_slots; j++) {
202 if (! board->slots[j].has_card)
204 for (k = j+1; k < board->num_slots; k++) {
205 if (! board->slots[k].has_card)
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))
216 board->sets_possible = sets_possible;
220 board_init (board_t *board)
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;
229 board_count_sets_possible (board);
233 deal (deck_t *deck, board_t *board)
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;
245 board_count_sets_possible (board);
249 set_game_handle_show (set_game_t *game,
253 LmMessageNode *html, *body, *span;
255 GError *error = NULL;
256 board_t *board = &game->board;
257 char board_str[BOARD_MAX_SLOTS * (NUM_ATTRIBUTES + 1)];
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,
265 "http://jabber.org/protocol/xhtml-im");
266 body = lm_message_node_add_child (html, "body", "");
267 lm_message_node_set_attribute (body,
269 "http://www.w3.org/1999/xhtml");
272 for (slot = 0; slot < board->num_slots; slot++) {
274 lm_message_node_add_child (body, "br", "");
275 if (board->slots[slot].has_card) {
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]) {
285 case SYMBOL_SQUIGGLE:
299 span = lm_message_node_add_child (body, "span",
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);
310 for (attr = 0; attr < NUM_ATTRIBUTES; attr++)
311 *s++ = '0' + board->slots[slot].card.attributes[attr];
314 span = lm_message_node_add_child (body, "span", " ");
315 lm_message_node_set_attribute (span, "style",
316 "font-family: Monospace;");
325 lm_message_node_add_child (reply->node, "body", board_str);
327 result = lm_connection_send (game->lg.connection, reply, &error);
328 lm_message_unref (reply);
331 g_error ("lm_connection_send failed: %s\n",
333 loudgame_quit (&game->lg, 1);
338 set_game_handle_hint (set_game_t *game,
341 loudgame_sendf (&game->lg, peer, "Sets possible: %d",
342 game->board.sets_possible);
346 set_game_handle_shuffle (set_game_t *game,
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);
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);
361 set_game_shuffle (game);
366 set_game_handle_set (set_game_t *game,
378 while (*s && i < 3) {
379 slots[i++] = strtoul (s, &end, 10);
381 loudgame_sendf (&game->lg, peer,
382 "Error: Not an integer: %s", s);
388 while (end && *end && isspace (*end))
391 if (i != 3 || *end != '\0') {
392 loudgame_sendf (&game->lg, peer,
393 "Error: The 'set' command requires exactly 3 integers");
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);
406 if (slots[0] == slots[1] ||
407 slots[0] == slots[2] ||
408 slots[1] == slots[2])
410 loudgame_sendf (&game->lg, peer,
411 "Error: All 3 values must be unique");
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;
419 loudgame_sendf (&game->lg, peer,
420 "Error: There's no card at position %d", i);
425 if (! is_set (cards, 3)) {
426 loudgame_sendf (&game->lg, peer,
427 "Sorry, that's not a set");
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.",
436 slots[0], slots[1], slots[2]);
438 for (i = 0; i < 3; i++)
439 game->board.slots[slots[i]].has_card = 0;
441 board_count_sets_possible (&game->board);
443 deal (&game->deck, &game->board);
447 set_game_handle_help (set_game_t *game,
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"
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"
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.");
469 set_game_handle_message (loudgame_t *lg,
473 set_game_t *game = (set_game_t*) lg;
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);
486 loudgame_sendf (lg, peer, "Unknown command: '%s'. Use 'help' to get a list of valid commands.", message);
489 /* Begin a new game */
491 set_game_new_game (set_game_t *game)
493 deck_init (&game->deck);
494 board_init (&game->board);
495 deal (&game->deck, &game->board);
499 set_game_init (set_game_t *game, int argc, char *argv[])
503 err = loudgame_init (&game->lg, argc, argv);
507 set_game_new_game (game);
512 /* Return the dealt cards to the deck, reshuffle, and deal again. */
514 set_game_shuffle (set_game_t *game)
516 board_t *board = &game->board;
517 deck_t *deck = &game->deck;
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;
535 main (int argc, char *argv[])
542 err = set_game_init (&game, argc, argv);
546 game.lg.handle_message = set_game_handle_message;
548 err = loudgame_run (&game.lg);