]> git.cworth.org Git - loudgame/blobdiff - lg-set.c
lg-set: Add a broadcast whenever a user finds a set.
[loudgame] / lg-set.c
index 145120092f20a91cb8d8aead43e9e1313861b5f0..a6594b27a3de4bc9ad9a621f68755f97e9a985ec 100644 (file)
--- a/lg-set.c
+++ b/lg-set.c
  * Author: Carl Worth <cworth@cworth.org>
  */
 
+#include "loudgame.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <math.h>
 #include <time.h>
+#include <ctype.h>
 
 #include <assert.h>
 
@@ -73,22 +76,24 @@ typedef struct slot {
 typedef struct board {
     int num_slots;
     slot_t slots[BOARD_MAX_SLOTS];
-    int needs_deal;
     int sets_possible;
-    int display_sets_possible;
 } board_t;
 
-typedef struct game {
+typedef struct _set_game {
+    loudgame_t lg;
     deck_t deck;
     board_t board;
-} game_t;
-
-static void game_init (game_t *game);
-static void game_fini (game_t *game);
+} set_game_t;
 
 static void
 board_count_sets_possible (board_t *board);
 
+static int
+set_game_shuffle (set_game_t *game);
+
+static void
+set_game_new_game (set_game_t *game);
+
 static int
 attribute_all_same (card_t *cards, int num_cards, int attr)
 {
@@ -140,34 +145,6 @@ is_set (card_t *cards, int num_cards)
     return 1;
 }
 
-static int
-check_selected_for_set (board_t *board)
-{
-    int i, num_selected;
-    card_t cards[3];
-
-    num_selected = 0;
-    for (i=0; i < board->num_slots; i++) {
-       if (board->slots[i].selected) {
-           if (num_selected >= 3)
-               return 0;
-           cards[num_selected++] = board->slots[i].card;
-       }
-    }
-
-    if (num_selected !=3)
-       return 0;
-
-    if (! is_set (cards, num_selected))
-       return 0;
-
-    board_count_sets_possible (board);
-
-    board->needs_deal = 1;
-
-    return 1;
-}
-
 static void
 deck_shuffle (deck_t *deck)
 {
@@ -248,32 +225,10 @@ board_init (board_t *board)
        board->slots[i].has_card = 0;
        board->slots[i].selected = 0;
     }
-    board->needs_deal = 0;
 
     board_count_sets_possible (board);
 }
 
-static void
-board_print (board_t *board)
-{
-    int i, j;
-
-    printf ("Sets possible: %d\n", board->sets_possible);
-
-    for  (i = 0; i < board->num_slots; i++) {
-       if (! board->slots[i].has_card)
-           for (j = 0; j < NUM_ATTRIBUTES; j++)
-               printf (" ");
-       else
-           for (j = 0; j < NUM_ATTRIBUTES; j++)
-               printf ("%d", board->slots[i].card.attributes[j]);
-       if ((i + 1) % 3 == 0)
-           printf ("\n");
-       else
-           printf (" ");
-    }
-}
-
 static void
 deal (deck_t *deck, board_t *board)
 {
@@ -288,35 +243,277 @@ deal (deck_t *deck, board_t *board)
        }
 
     board_count_sets_possible (board);
+}
+
+static void
+set_game_handle_show (set_game_t       *game,
+                     const char        *peer)
+{
+    LmMessage *reply;
+    LmMessageNode *html, *body, *span;
+    gboolean result;
+    GError *error = NULL;
+    board_t *board = &game->board;
+    char board_str[BOARD_MAX_SLOTS * (NUM_ATTRIBUTES + 1)];
+    char *s;
+    int slot, pos, attr;
+
+    reply = lm_message_new (peer, LM_MESSAGE_TYPE_MESSAGE);
+    html = lm_message_node_add_child (reply->node, "html", "");
+    lm_message_node_set_attribute (html,
+                                  "xmlns",
+                                  "http://jabber.org/protocol/xhtml-im");
+    body = lm_message_node_add_child (html, "body", "");
+    lm_message_node_set_attribute (body,
+                                  "xmlns",
+                                  "http://www.w3.org/1999/xhtml");
+
+    s = board_str;
+    for (slot = 0; slot < board->num_slots; slot++) {
+       if (slot % 4 == 0)
+           lm_message_node_add_child (body, "br", "");
+       if (board->slots[slot].has_card) {
+           char card_str[5];
+           char *style;
+           card_t card = board->slots[slot].card;
+           for (pos = 0; pos < 3; pos++) {
+               if (pos <= card.attributes[ATTRIBUTE_INDEX_NUMBER]) {
+                   switch (card.attributes[ATTRIBUTE_INDEX_SYMBOL]) {
+                   case SYMBOL_OVAL:
+                       card_str[pos] = 'O';
+                       break;
+                   case SYMBOL_SQUIGGLE:
+                       card_str[pos] = 'S';
+                       break;
+                   case SYMBOL_DIAMOND:
+                   default:
+                       card_str[pos] = 'X';
+                       break;
+                   }
+               } else {
+                   card_str[pos] = ' ';
+               }
+           }
+           card_str[3] = ' ';
+           card_str[4] = '\0';
+           span = lm_message_node_add_child (body, "span",
+                                             card_str);
+           style = g_strdup_printf ("font-family: Monospace; color: %s;%s%s",
+                                    attribute_values[ATTRIBUTE_INDEX_COLOR][
+                                        card.attributes[ATTRIBUTE_INDEX_COLOR]],
+                                    card.attributes[ATTRIBUTE_INDEX_SHADING]
+                                    == SHADING_SOLID ? "font-weight: bold;" : "",
+                                    card.attributes[ATTRIBUTE_INDEX_SHADING]
+                                    == SHADING_STRIPED ? "text-decoration: underline;" : "");
+           lm_message_node_set_attribute (span, "style", style);
+           free (style);
+           for (attr = 0; attr < NUM_ATTRIBUTES; attr++)
+               *s++ = '0' + board->slots[slot].card.attributes[attr];
+           *s++ = ' ';
+       } else {
+           span = lm_message_node_add_child (body, "span", "    ");
+           lm_message_node_set_attribute (span, "style",
+                                          "font-family: Monospace;");
+           *s++ = '-';
+           *s++ = '-';
+           *s++ = '-';
+           *s++ = ' ';
+       }
+    }
+    *s = '\0';
+
+    lm_message_node_add_child (reply->node, "body", board_str);
+
+    result = lm_connection_send (game->lg.connection, reply, &error);
+    lm_message_unref (reply);
+
+    if (! result) {
+       g_error ("lm_connection_send failed: %s\n",
+                error->message);
+       loudgame_quit (&game->lg, 1);
+    }
+}
 
-    board->needs_deal = 0;
+static void
+set_game_handle_hint (set_game_t *game,
+                     const char *peer)
+{
+    loudgame_sendf (&game->lg, peer, "Sets possible: %d",
+                   game->board.sets_possible);
+}
+
+static void
+set_game_handle_shuffle (set_game_t *game,
+                        const char *peer)
+{
+    if (game->board.sets_possible) {
+       loudgame_sendf (&game->lg, peer,
+                       "There are %d sets, refusing to shuffle.",
+                       game->board.sets_possible);
+       return;
+    }
+
+    if (game->deck.num_cards == 0) {
+       loudgame_sendf (&game->lg, peer,
+                       "Deck exhausted, starting a new game.");
+       set_game_new_game (game);
+    } else {
+       set_game_shuffle (game);
+    }
+}
+
+static void
+set_game_handle_set (set_game_t        *game,
+                    const char *peer,
+                    const char *message)
+{
+    const char *s;
+    char *end = NULL;
+    int i;
+    int slots[3];
+    card_t cards[3];
+
+    s = message;
+    i = 0;
+    while (*s && i < 3) {
+       slots[i++] = strtoul (s, &end, 10);
+       if (end == s) {
+           loudgame_sendf (&game->lg, peer,
+                           "Error: Not an integer: %s", s);
+           return;
+       }
+       s = end;
+    }
+
+    while (end && *end && isspace (*end))
+       end++;
+
+    if (i != 3 || *end != '\0') {
+       loudgame_sendf (&game->lg, peer,
+                       "Error: The 'set' command requires exactly 3 integers");
+       return;
+    }
+
+    for (i = 0; i < 3; i++) {
+       if (slots[i] < 0 || slots[i] > BOARD_MAX_SLOTS) {
+           loudgame_sendf (&game->lg, peer,
+                           "Error: Value %d is out of range (0-%d)",
+                           slots[i], BOARD_MAX_SLOTS);
+           return;
+       }
+    }
+
+    if (slots[0] == slots[1] ||
+       slots[0] == slots[2] ||
+       slots[1] == slots[2])
+    {
+           loudgame_sendf (&game->lg, peer,
+                           "Error: All 3 values must be unique");
+           return;
+    }
+
+    for (i=0; i < 3; i++) {
+       if (game->board.slots[slots[i]].has_card) {
+           cards[i] = game->board.slots[slots[i]].card;
+       } else {
+           loudgame_sendf (&game->lg, peer,
+                           "Error: There's no card at position %d", i);
+           return;
+       }
+    }
+
+    if (! is_set (cards, 3)) {
+       loudgame_sendf (&game->lg, peer,
+                       "Sorry, that's not a set");
+       return;
+    }
+
+    loudgame_sendf (&game->lg, peer,
+                   "Yes, that's a set!");
+    loudgame_broadcastf (&game->lg,
+                        "%s found the set: %d %d %d. Please send a new 'show' command.",
+                        peer,
+                        slots[0], slots[1], slots[2]);
+
+    for (i = 0; i < 3; i++)
+       game->board.slots[slots[i]].has_card = 0;
+
+    board_count_sets_possible (&game->board);
+
+    deal (&game->deck, &game->board);
+}
+
+static void
+set_game_handle_help (set_game_t *game,
+                     const char *peer)
+{
+    loudgame_sendf (&game->lg, peer,
+                   "I'm a bot that allows you to play the game of SET.\n"
+                   "Here are some commands I understand:\n"
+                   "\tshow     \tShow the current cards on the board\n"
+                   "\thint     \tIndicate how many sets are currently possible\n"
+                   "\tshuffle  \tReturn the cards to the deck, shuffle and deal\n"
+                   "\t         \t(this is only allowed if no sets are possible)\n"
+                   "\tset X Y Z\tClaim three cards as a set. The cards are numbered\n"
+                   "\t         \tleft-to-right, and top-to-bottom from 0 to 11.\n"
+                   "\n"
+                   "For more information about SET, or to purchase your own deck, visit\n"
+                   "http://setgame.com . Please note that this server is unaffiliated\n"
+                   "with Set Enterprises, Inc., who publish the SET card game.");
+}
+
+static void
+set_game_handle_message (loudgame_t *lg,
+                        const char *peer,
+                        const char *message)
+{
+    set_game_t *game = (set_game_t*) lg;
+
+    if (strcmp (message, "show") == 0)
+       set_game_handle_show (game, peer);
+    else if (strcmp (message, "help") == 0)
+       set_game_handle_help (game, peer);
+    else if (strcmp (message, "hint") == 0)
+       set_game_handle_hint (game, peer);
+    else if (strcmp (message, "shuffle") == 0)
+       set_game_handle_shuffle (game, peer);
+    else if (strncmp (message, "set", 3) == 0)
+       set_game_handle_set (game, peer, message + 3);
+    else
+       loudgame_sendf (lg, peer, "Unknown command: '%s'. Use 'help' to get a list of valid commands.", message);
 }
 
 /* Begin a new game */
 static void
-game_init (game_t *game)
+set_game_new_game (set_game_t *game)
 {
     deck_init (&game->deck);
     board_init (&game->board);
     deal (&game->deck, &game->board);
 }
 
-static void
-game_fini (game_t *game)
+static int
+set_game_init (set_game_t *game, int argc, char *argv[])
 {
-    /* Nothing to do. */
+    int err;
+
+    err = loudgame_init (&game->lg, argc, argv);
+    if (err)
+       return err;
+
+    set_game_new_game (game);
+
+    return 0;
 }
 
 /* Return the dealt cards to the deck, reshuffle, and deal again. */
 static int
-reshuffle (deck_t *deck, board_t *board)
+set_game_shuffle (set_game_t *game)
 {
+    board_t *board = &game->board;
+    deck_t *deck = &game->deck;
     int i;
 
-    if (board->sets_possible) {
-       return 0;
-    }
-
     for (i=0; i < board->num_slots; i++) {
        if (board->slots[i].has_card) {
            deck->cards[++deck->num_cards - 1] = board->slots[i].card;
@@ -332,17 +529,22 @@ reshuffle (deck_t *deck, board_t *board)
 }
 
 int
-main (void)
+main (int argc, char *argv[])
 {
-    game_t game;
+    set_game_t game;
+    int err;
 
     srand (time(0));
 
-    game_init (&game);
+    err = set_game_init (&game, argc, argv);
+    if (err)
+       return err;
 
-    board_print (&game.board);
+    game.lg.handle_message = set_game_handle_message;
 
-    game_fini (&game);
+    err = loudgame_run (&game.lg);
+    if (err)
+       return err;
 
     return 0;
 }