added blackjack game
[NeonServV5.git] / src / modules / NeonFun.mod / game_blackjack.c
diff --git a/src/modules/NeonFun.mod/game_blackjack.c b/src/modules/NeonFun.mod/game_blackjack.c
new file mode 100644 (file)
index 0000000..6c69934
--- /dev/null
@@ -0,0 +1,419 @@
+/* game_blackjack.c - NeonServ v5.6
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+#include "../module.h"
+#include "game_blackjack.h"
+#include "bot_NeonFun.h"
+#include "../../IRCParser.h"
+#include "../../bots.h"
+#include "../../UserNode.h"
+#include "../../ChanUser.h"
+#include "../../tools.h"
+#include "../botid.h"
+
+struct bj_game *bj_active_games = NULL;
+
+static int bj_ctype[] =         {BJ_CTYPE_HEARTS, BJ_CTYPE_SPADES, BJ_CTYPE_DIAMONDS, BJ_CTYPE_CLUBS};
+static int bj_irc_colors[] =    {4,               1,               4,                 1};
+static char *bj_color_chars[] = {"\xE2\x9D\xA4",  "\xE2\x99\xA0",  "\xE2\x99\xA6",    "\xE2\x99\xA3"};
+
+static const struct {
+    const char *name;
+    unsigned char type;
+    unsigned int points : 8;
+} bj_card_types[] = {
+    {"2",  BJ_CARD_NUMBER_2,  2},
+    {"3",  BJ_CARD_NUMBER_3,  3},
+    {"4",  BJ_CARD_NUMBER_4,  4},
+    {"5",  BJ_CARD_NUMBER_5,  5},
+    {"6",  BJ_CARD_NUMBER_6,  6},
+    {"7",  BJ_CARD_NUMBER_7,  7},
+    {"8",  BJ_CARD_NUMBER_8,  8},
+    {"9",  BJ_CARD_NUMBER_9,  9},
+    {"10", BJ_CARD_NUMBER_10, 10},
+    {"J",  BJ_CARD_JACK,      10},
+    {"Q",  BJ_CARD_QUEEN,     10},
+    {"K",  BJ_CARD_KING,      10},
+    {"A",  BJ_CARD_ACE,       11},
+    {NULL, 0}
+};
+
+struct bj_card_deck *bj_shuffle_deck() {
+    struct bj_card *card, *last_card = NULL;
+    int card_count = 0;
+    #define ADD_CARD(ctype,ccard) \
+    card = malloc(sizeof(*card)); \
+    card->type = ctype; \
+    card->card = ccard; \
+    card->prev = NULL; \
+    card->next = last_card; \
+    if(last_card) \
+        last_card->prev = card; \
+    last_card = card; \
+    card_count++;
+    int typecount = BJ_CTYPE_COUNT;
+    for(typecount--; typecount >= 0; typecount--) {
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_2);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_3);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_4);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_5);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_6);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_7);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_8);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_9);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_NUMBER_10);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_JACK);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_QUEEN);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_KING);
+        ADD_CARD(bj_ctype[typecount], BJ_CARD_ACE);
+    }
+    #undef ADD_CARD
+    struct bj_card_deck *deck = malloc(sizeof(*deck));
+    deck->cards = last_card;
+    deck->count = card_count;
+    return deck;
+}
+
+struct bj_card *bj_get_card(struct bj_card_deck *deck) {
+    if(!deck->count) return NULL;
+    int card_id = (rand() % deck->count);
+    int i = 0;
+    struct bj_card *card;
+    for(card = deck->cards; card; card = card->next) {
+        if(i == card_id) {
+            if(card->prev)
+                card->prev->next = card->next;
+            else
+                deck->cards = card->next;
+            if(card->next)
+                card->next->prev = card->prev;
+            deck->count--;
+            card->next = NULL;
+            card->prev = NULL;
+            return card;
+        }
+        i++;
+    }
+    return NULL;
+}
+
+void bj_free_deck(struct bj_card_deck *deck) {
+    struct bj_card *card, *next_card;
+    if(deck->count) {
+        for(card = deck->cards; card; card = next_card) {
+            next_card = card->next;
+            free(card);
+        }
+    }
+    free(deck);
+}
+
+void bj_free_player(struct bj_player *player, struct bj_card_deck *deck) {
+    if(player->count) {
+        struct bj_card *card, *next_card;
+        for(card = player->cards; card; card = next_card) {
+            next_card = card->next;
+            if(deck) {
+                card->next = deck->cards;
+                deck->cards->prev = card;
+                deck->cards = card;
+                deck->count++;
+            } else
+                free(card);
+        }
+    }
+    if(player->prev)
+        player->prev->next = player->next;
+    if(player->next)
+        player->next->prev = player->prev;
+    free(player);
+}
+
+void bj_free_game(struct bj_game *game) {
+    struct bj_player *player, *next_player;
+    for(player = game->player; player; player = next_player) {
+        next_player = player->next;
+        bj_free_player(player, NULL);
+    }
+    if(game->deck)
+        bj_free_deck(game->deck);
+    if(game->timer)
+        timeq_del(game->timer);
+    struct bj_game *cgame, *pgame = NULL;
+    for(cgame = bj_active_games; cgame; cgame = cgame->next) {
+        if(cgame == game) {
+            if(pgame)
+                pgame->next = game->next;
+            else
+                bj_active_games = game->next;
+            break;
+        } else
+            pgame = cgame;
+    }
+    free(game);
+}
+
+struct bj_player *bj_get_next_player(struct bj_game *game) {
+    struct bj_player *player = game->active_player;
+    player = player->next;
+    return player;
+}
+
+int bj_get_player_card_points(struct bj_player *player) {
+    int i, points = 0, ace_count = 0;
+    struct bj_card *card;
+    for(card = player->cards; card; card = card->next) {
+        if(card->card == BJ_CARD_ACE) {
+            ace_count++; //add these points later ;)
+            continue;
+        }
+        i = 0;
+        while(bj_card_types[i].name) {
+            if(bj_card_types[i].type == card->card) {
+                points += bj_card_types[i].points;
+                break;
+            }
+            i++;
+        }
+    }
+    while((ace_count--) > 0) {
+        if(points <= 10)
+            points += 11;
+        else
+            points += 1;
+    }
+    return points;
+}
+
+static void bj_print_player_cards(struct bj_player *player, char *cards_buf) {
+    struct bj_card *card;
+    int cards_bufpos = 0;
+    for(card = player->cards; card; card = card->next) {
+        int cardcolor = 1;
+        char *cardchar = "";
+        int i;
+        for(i = 0; i < BJ_CTYPE_COUNT; i++) {
+            if(bj_ctype[i] == card->type) {
+                cardcolor = bj_irc_colors[i];
+                cardchar = bj_color_chars[i];
+                break;
+            }
+        }
+        i = 0;
+        while(bj_card_types[i].name) {
+            if(bj_card_types[i].type == card->card) {
+                cards_bufpos += sprintf(cards_buf + cards_bufpos, "%s[\003%d%s%s\003]", (cards_bufpos ? " " : ""), cardcolor, cardchar, bj_card_types[i].name);
+                break;
+            }
+            i++;
+        }
+    }
+}
+
+void bj_show_player_cards(struct bj_game *game, struct bj_player *player) {
+    char cards_buf[MAXLEN];
+    bj_print_player_cards(player, cards_buf);
+    int card_points = bj_get_player_card_points(player);
+    reply(game->textbot, player->chanuser->user, "NF_BJ_YOUR_CARDS", card_points, cards_buf);
+}
+
+TIMEQ_CALLBACK(bj_game_wait_timeout) {
+    struct bj_game *game = data;
+    game->timer = NULL;
+    if(game->players == 1) {
+        bj_reply(game, NULL, "NF_BJ_LESS_PLAYERS");
+        bj_free_game(game);
+        return;
+    }
+    game->deck = bj_shuffle_deck();
+    bj_reply(game, NULL, "NF_BJ_START");
+    game->state = BJ_STATE_RUNNING;
+    game->active_player = game->player; //active player
+    bj_reply(game, NULL, "NF_BJ_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+    game->timer = timeq_add(30, module_id, bj_player_timeout, game);
+}
+
+TIMEQ_CALLBACK(bj_player_timeout) {
+    struct bj_game *game = data;
+    game->timer = NULL;
+    //player timeout (next player)
+    struct bj_player *player = game->active_player;
+    bj_reply(game, NULL, "NF_BJ_USER_TIMEOUT", player->chanuser->user->nick);
+    bj_action_next_player(game, player);
+}
+
+void bj_action_take_card(struct bj_game *game, struct bj_player *player) {
+    int points = bj_get_player_card_points(player);
+    if(points > 21) {
+        reply(game->textbot, player->chanuser->user, "NF_BJ_POINTS_EXCEEDED");
+        return;
+    }
+    if(game->timer) {
+        timeq_del(game->timer);
+        game->timer = NULL;
+    }
+    struct bj_card *card = bj_get_card(game->deck);
+    if(!card) {
+        bj_free_deck(game->deck);
+        game->deck = bj_shuffle_deck();
+        card = bj_get_card(game->deck);
+    }
+    struct bj_card *last_card;
+    for(last_card = player->cards; last_card; last_card = last_card->next) {
+        if(last_card->next == NULL)
+            break;
+    }
+    card->prev = last_card;
+    if(last_card)
+        last_card->next = card;
+    else
+        player->cards = card;
+    player->count++;
+    char cardbuf[16];
+    int cardcolor = 1;
+    char *cardchar = "";
+    int i, cardpoints = 0;
+    cardbuf[0] = '\0';
+    for(i = 0; i < BJ_CTYPE_COUNT; i++) {
+        if(bj_ctype[i] == card->type) {
+            cardcolor = bj_irc_colors[i];
+            cardchar = bj_color_chars[i];
+            break;
+        }
+    }
+    i = 0;
+    while(bj_card_types[i].name) {
+        if(bj_card_types[i].type == card->card) {
+            sprintf(cardbuf, "[\003%d%s%s\003]", cardcolor, cardchar, bj_card_types[i].name);
+            cardpoints = bj_card_types[i].points;
+            if(bj_card_types[i].type == BJ_CARD_ACE && points > 10)
+                cardpoints = 1;
+            break;
+        }
+        i++;
+    }
+    reply(game->textbot, player->chanuser->user, "NF_BJ_TAKE", cardbuf, cardpoints);
+    bj_show_player_cards(game, player);
+    if(points + cardpoints > 21)
+        reply(game->textbot, player->chanuser->user, "NF_BJ_POINTS_EXCEEDED");
+    game->timer = timeq_add(30, module_id, bj_player_timeout, game);
+}
+
+void bj_action_next_player(struct bj_game *game, struct bj_player *player) {
+    if(game->timer) {
+        timeq_del(game->timer);
+        game->timer = NULL;
+    }
+    if(player)
+        bj_show_player_cards(game, player);
+    game->active_player = bj_get_next_player(game);
+    if(game->active_player) {
+        bj_reply(game, NULL, "NF_BJ_USER_HURRY_UP", game->active_player->chanuser->user->nick);
+        game->timer = timeq_add(30, module_id, bj_player_timeout, game);
+    } else {
+        bj_game_end(game);
+        bj_free_game(game);
+    }
+}
+
+void bj_event_part(struct ChanUser *chanuser) {
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(chanuser->chan == game->channel) {
+            struct bj_player *player;
+            for(player = game->player; player; player = player->next) {
+                if(player->chanuser == chanuser) {
+                    if(game->active_player == player) {
+                        if(bj_get_next_player(game) == NULL) {
+                            bj_game_end(game);
+                            bj_free_game(game);
+                            return;
+                        } else
+                            bj_action_next_player(game, NULL);
+                    }
+                    game->players--;
+                    bj_free_player(player, game->deck);
+                    return;
+                }
+            }
+        }
+    }
+}
+
+void bj_event_freechan(struct ChanNode *chan) {
+    struct bj_game *game;
+    for(game = bj_active_games; game; game = game->next) {
+        if(game->channel == chan) {
+            bj_free_game(game);
+            return;
+        }
+    }
+}
+
+static int bj_highscore_sort(const void *a, const void *b) {
+    const struct bj_player *player_a = *((struct bj_player * const *) a);
+    const struct bj_player *player_b = *((struct bj_player * const *) b); 
+    int points_a, points_b;
+    if(player_a->count > 21)
+        points_a = player_a->count * -1;
+    else
+        points_a = player_a->count;
+    if(player_b->count > 21)
+        points_b = player_b->count * -1;
+    else
+        points_b = player_b->count;
+    return points_b - points_a;
+}
+
+void bj_game_end(struct bj_game *game) {
+    bj_reply(game, NULL, "NF_BJ_GAME_FINISHED");
+    struct Table *table;
+    table = table_init(4, game->players+1, 0);
+    char *content[4];
+    content[0] = get_language_string(NULL, "NF_BJ_RANK");
+    content[1] = get_language_string(NULL, "NF_BJ_NAME");
+    content[2] = get_language_string(NULL, "NF_BJ_POINTS");
+    content[3] = get_language_string(NULL, "NF_BJ_CARDS");
+    table_add(table, content);
+    //sort users
+    struct bj_player *players[game->players];
+    struct bj_player *player;
+    int i = 0;
+    for(player = game->player; player; player = player->next) {
+        player->count = bj_get_player_card_points(player);
+        players[i++] = player;
+    }
+    qsort(players, game->players, sizeof(struct bj_player *), bj_highscore_sort);
+    char rankbuf[12];
+    char pointbuf[12];
+    char cardsbuf[MAXLEN];
+    for(i = 0; i < game->players; i++) {
+        player = players[i];
+        sprintf(rankbuf, "#%d", i+1);
+        content[0] = rankbuf;
+        content[1] = player->chanuser->user->nick;
+        sprintf(pointbuf, "%d", player->count);
+        content[2] = pointbuf;
+        bj_print_player_cards(player, cardsbuf);
+        content[3] = cardsbuf;
+        table_add(table, content);
+    }
+    char **table_lines = table_end(table);
+    for(i = 0; i < table->entrys; i++) {
+        bj_reply(game, NULL, table_lines[i]);
+    }
+    table_free(table);
+}