skylib_fluffos_v3/
skylib_fluffos_v3/bin/
skylib_fluffos_v3/bin/db/
skylib_fluffos_v3/fluffos-2.9-ds2.04/
skylib_fluffos_v3/fluffos-2.9-ds2.04/ChangeLog.old/
skylib_fluffos_v3/fluffos-2.9-ds2.04/Win32/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/simuls/
skylib_fluffos_v3/fluffos-2.9-ds2.04/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/clone/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/command/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/data/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/etc/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/master/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/log/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/compiler/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/efuns/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/operators/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/u/
skylib_fluffos_v3/fluffos-2.9-ds2.04/tmp/
skylib_fluffos_v3/fluffos-2.9-ds2.04/windows/
skylib_fluffos_v3/mudlib/
skylib_fluffos_v3/mudlib/cmds/
skylib_fluffos_v3/mudlib/cmds/admin/
skylib_fluffos_v3/mudlib/cmds/guild-race/
skylib_fluffos_v3/mudlib/cmds/living/broken/
skylib_fluffos_v3/mudlib/cmds/player/group_cmds/
skylib_fluffos_v3/mudlib/cmds/playtester/
skylib_fluffos_v3/mudlib/d/admin/
skylib_fluffos_v3/mudlib/d/admin/room/
skylib_fluffos_v3/mudlib/d/admin/room/we_care/
skylib_fluffos_v3/mudlib/d/admin/save/
skylib_fluffos_v3/mudlib/d/admin/text/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/buildings/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/map/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/roads/
skylib_fluffos_v3/mudlib/d/learning/chars/
skylib_fluffos_v3/mudlib/d/learning/functions/
skylib_fluffos_v3/mudlib/d/learning/handlers/
skylib_fluffos_v3/mudlib/d/learning/help_topics/
skylib_fluffos_v3/mudlib/d/learning/help_topics/npcs/
skylib_fluffos_v3/mudlib/d/learning/help_topics/objects/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/crowd/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/situations/
skylib_fluffos_v3/mudlib/d/learning/save/
skylib_fluffos_v3/mudlib/d/learning/school/
skylib_fluffos_v3/mudlib/d/learning/school/add_sc/
skylib_fluffos_v3/mudlib/d/learning/school/characters/
skylib_fluffos_v3/mudlib/d/learning/school/general/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/basic_commands/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/edtutor/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_fluffos_v3/mudlib/d/learning/school/items/
skylib_fluffos_v3/mudlib/d/learning/school/npc_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/room_basic/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/situations/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_fluffos_v3/mudlib/d/learning/text/
skylib_fluffos_v3/mudlib/d/liaison/
skylib_fluffos_v3/mudlib/d/mudlib/
skylib_fluffos_v3/mudlib/d/mudlib/changes/
skylib_fluffos_v3/mudlib/d/playtesters/
skylib_fluffos_v3/mudlib/d/playtesters/effects/
skylib_fluffos_v3/mudlib/d/playtesters/handlers/
skylib_fluffos_v3/mudlib/d/playtesters/items/
skylib_fluffos_v3/mudlib/d/sage/
skylib_fluffos_v3/mudlib/doc/
skylib_fluffos_v3/mudlib/doc/creator/
skylib_fluffos_v3/mudlib/doc/driver/
skylib_fluffos_v3/mudlib/doc/driver/efuns/arrays/
skylib_fluffos_v3/mudlib/doc/driver/efuns/buffers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/calls/
skylib_fluffos_v3/mudlib/doc/driver/efuns/compile/
skylib_fluffos_v3/mudlib/doc/driver/efuns/filesystem/
skylib_fluffos_v3/mudlib/doc/driver/efuns/floats/
skylib_fluffos_v3/mudlib/doc/driver/efuns/functions/
skylib_fluffos_v3/mudlib/doc/driver/efuns/general/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mappings/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mixed/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mudlib/
skylib_fluffos_v3/mudlib/doc/driver/efuns/numbers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/parsing/
skylib_fluffos_v3/mudlib/doc/login/
skylib_fluffos_v3/mudlib/doc/lpc/basic_manual/
skylib_fluffos_v3/mudlib/doc/lpc/intermediate/
skylib_fluffos_v3/mudlib/doc/new/add_command/
skylib_fluffos_v3/mudlib/doc/new/events/
skylib_fluffos_v3/mudlib/doc/new/handlers/
skylib_fluffos_v3/mudlib/doc/new/living/race/
skylib_fluffos_v3/mudlib/doc/new/living/spells/
skylib_fluffos_v3/mudlib/doc/new/object/
skylib_fluffos_v3/mudlib/doc/new/player/
skylib_fluffos_v3/mudlib/doc/new/room/guild/
skylib_fluffos_v3/mudlib/doc/new/room/outside/
skylib_fluffos_v3/mudlib/doc/new/room/storeroom/
skylib_fluffos_v3/mudlib/doc/object/
skylib_fluffos_v3/mudlib/doc/playtesters/
skylib_fluffos_v3/mudlib/doc/policy/
skylib_fluffos_v3/mudlib/doc/weapons/
skylib_fluffos_v3/mudlib/global/
skylib_fluffos_v3/mudlib/global/creator/
skylib_fluffos_v3/mudlib/handlers/
skylib_fluffos_v3/mudlib/include/casino/
skylib_fluffos_v3/mudlib/include/cmds/
skylib_fluffos_v3/mudlib/include/effects/
skylib_fluffos_v3/mudlib/include/npc/
skylib_fluffos_v3/mudlib/include/room/
skylib_fluffos_v3/mudlib/include/shops/
skylib_fluffos_v3/mudlib/net/daemon/
skylib_fluffos_v3/mudlib/net/daemon/chars/
skylib_fluffos_v3/mudlib/net/inherit/
skylib_fluffos_v3/mudlib/net/obj/
skylib_fluffos_v3/mudlib/net/obj/BACKUPS/
skylib_fluffos_v3/mudlib/obj/amulets/
skylib_fluffos_v3/mudlib/obj/armours/plate/
skylib_fluffos_v3/mudlib/obj/b_day/
skylib_fluffos_v3/mudlib/obj/clothes/transport/horse/
skylib_fluffos_v3/mudlib/obj/faith/symbols/
skylib_fluffos_v3/mudlib/obj/fungi/
skylib_fluffos_v3/mudlib/obj/gatherables/
skylib_fluffos_v3/mudlib/obj/instruments/
skylib_fluffos_v3/mudlib/obj/media/
skylib_fluffos_v3/mudlib/obj/misc/player_shop/
skylib_fluffos_v3/mudlib/obj/monster/godmother/
skylib_fluffos_v3/mudlib/obj/monster/transport/
skylib_fluffos_v3/mudlib/obj/rings/
skylib_fluffos_v3/mudlib/obj/scabbards/
skylib_fluffos_v3/mudlib/obj/spells/
skylib_fluffos_v3/mudlib/obj/stationery/
skylib_fluffos_v3/mudlib/obj/stationery/envelopes/
skylib_fluffos_v3/mudlib/obj/toys/
skylib_fluffos_v3/mudlib/obj/vessels/
skylib_fluffos_v3/mudlib/obj/weapons/axes/
skylib_fluffos_v3/mudlib/obj/weapons/chains/
skylib_fluffos_v3/mudlib/obj/weapons/maces/BACKUPS/
skylib_fluffos_v3/mudlib/save/autodoc/
skylib_fluffos_v3/mudlib/save/book_handler/
skylib_fluffos_v3/mudlib/save/books/history/calarien/
skylib_fluffos_v3/mudlib/save/mail/
skylib_fluffos_v3/mudlib/save/new_soul/data/
skylib_fluffos_v3/mudlib/save/parcels/
skylib_fluffos_v3/mudlib/save/playerinfo/
skylib_fluffos_v3/mudlib/save/players/d/
skylib_fluffos_v3/mudlib/save/players/s/
skylib_fluffos_v3/mudlib/save/random_names/
skylib_fluffos_v3/mudlib/save/random_names/data/
skylib_fluffos_v3/mudlib/save/terrains/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_desert/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_grassy_field/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_mountain/
skylib_fluffos_v3/mudlib/save/todo_lists/
skylib_fluffos_v3/mudlib/secure/
skylib_fluffos_v3/mudlib/secure/cmds/admin/
skylib_fluffos_v3/mudlib/secure/cmds/lord/
skylib_fluffos_v3/mudlib/secure/config/
skylib_fluffos_v3/mudlib/secure/handlers/autodoc/
skylib_fluffos_v3/mudlib/secure/handlers/intermud/
skylib_fluffos_v3/mudlib/secure/include/global/
skylib_fluffos_v3/mudlib/secure/save/
skylib_fluffos_v3/mudlib/secure/save/handlers/
skylib_fluffos_v3/mudlib/secure/std/
skylib_fluffos_v3/mudlib/secure/std/classes/
skylib_fluffos_v3/mudlib/secure/std/modules/
skylib_fluffos_v3/mudlib/std/creator/
skylib_fluffos_v3/mudlib/std/dom/
skylib_fluffos_v3/mudlib/std/effects/
skylib_fluffos_v3/mudlib/std/effects/external/
skylib_fluffos_v3/mudlib/std/effects/fighting/
skylib_fluffos_v3/mudlib/std/effects/magic/
skylib_fluffos_v3/mudlib/std/effects/magic/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/other/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/priest/
skylib_fluffos_v3/mudlib/std/effects/room/
skylib_fluffos_v3/mudlib/std/environ/
skylib_fluffos_v3/mudlib/std/guilds/
skylib_fluffos_v3/mudlib/std/guilds/old/
skylib_fluffos_v3/mudlib/std/languages/
skylib_fluffos_v3/mudlib/std/liquids/
skylib_fluffos_v3/mudlib/std/npc/
skylib_fluffos_v3/mudlib/std/npc/goals/
skylib_fluffos_v3/mudlib/std/npc/goals/basic/
skylib_fluffos_v3/mudlib/std/npc/goals/misc/
skylib_fluffos_v3/mudlib/std/npc/plans/
skylib_fluffos_v3/mudlib/std/npc/plans/basic/
skylib_fluffos_v3/mudlib/std/npc/types/
skylib_fluffos_v3/mudlib/std/npc/types/helper/
skylib_fluffos_v3/mudlib/std/npcs/
skylib_fluffos_v3/mudlib/std/outsides/
skylib_fluffos_v3/mudlib/std/races/shadows/
skylib_fluffos_v3/mudlib/std/room/basic/BACKUPS/
skylib_fluffos_v3/mudlib/std/room/basic/topography/
skylib_fluffos_v3/mudlib/std/room/controller/
skylib_fluffos_v3/mudlib/std/room/inherit/topography/
skylib_fluffos_v3/mudlib/std/room/topography/area/
skylib_fluffos_v3/mudlib/std/room/topography/iroom/
skylib_fluffos_v3/mudlib/std/room/topography/milestone/
skylib_fluffos_v3/mudlib/std/shadows/curses/
skylib_fluffos_v3/mudlib/std/shadows/disease/
skylib_fluffos_v3/mudlib/std/shadows/fighting/
skylib_fluffos_v3/mudlib/std/shadows/healing/
skylib_fluffos_v3/mudlib/std/shadows/magic/
skylib_fluffos_v3/mudlib/std/shadows/poison/
skylib_fluffos_v3/mudlib/std/shadows/room/
skylib_fluffos_v3/mudlib/std/shops/controllers/
skylib_fluffos_v3/mudlib/std/shops/objs/
skylib_fluffos_v3/mudlib/std/shops/player_shop/
skylib_fluffos_v3/mudlib/std/socket/
skylib_fluffos_v3/mudlib/std/soul/d/
skylib_fluffos_v3/mudlib/std/soul/e/
skylib_fluffos_v3/mudlib/std/soul/i/
skylib_fluffos_v3/mudlib/std/soul/j/
skylib_fluffos_v3/mudlib/std/soul/k/
skylib_fluffos_v3/mudlib/std/soul/l/
skylib_fluffos_v3/mudlib/std/soul/n/
skylib_fluffos_v3/mudlib/std/soul/o/
skylib_fluffos_v3/mudlib/std/soul/q/
skylib_fluffos_v3/mudlib/std/soul/r/
skylib_fluffos_v3/mudlib/std/soul/u/
skylib_fluffos_v3/mudlib/std/soul/v/
skylib_fluffos_v3/mudlib/std/soul/y/
skylib_fluffos_v3/mudlib/std/soul/z/
skylib_fluffos_v3/mudlib/std/stationery/
skylib_fluffos_v3/mudlib/w/
skylib_fluffos_v3/mudlib/w/default/
skylib_fluffos_v3/mudlib/w/default/armour/
skylib_fluffos_v3/mudlib/w/default/clothes/
skylib_fluffos_v3/mudlib/w/default/item/
skylib_fluffos_v3/mudlib/w/default/npc/
skylib_fluffos_v3/mudlib/w/default/room/
skylib_fluffos_v3/mudlib/w/default/weapon/
skylib_fluffos_v3/mudlib/www/
skylib_fluffos_v3/mudlib/www/java/
skylib_fluffos_v3/mudlib/www/secure/
skylib_fluffos_v3/mudlib/www/secure/lpc/advanced/
skylib_fluffos_v3/mudlib/www/secure/lpc/intermediate/
skylib_fluffos_v3/win32/
/**
 * A Blackjack inherit for Divided Sky.
 * @author Avelan, 3rd February 2002.
 * @changed Did some cosmetics and fixed a few bugs.
 * - Sandoz, 2002-2003.
 */

nosave inherit ROOM_OBJ;

#define BUSTED     -1
#define BLACKJACK  -2
#define NAMES      ([ 11 : "Jack", 12 : "Queen", 13 : "King", 14 : "Ace" ])

#define TYPES      ([ "d" : "Diamonds", \
                      "c" : "Clubs",    \
                      "s" : "Spades",   \
                      "h" : "Hearts",   \
                      ])

#define CARDS      ({ "02", "03", "04", "05", "06", "07", \
                      "08", "09", "10", "11", "12", "13", "14", \
                      })

#define MSTRING(x) MONEY_H->money_value_string( x, query_place() )
#define MARRAY(x)  MONEY_H->create_money_array( x, query_place() )

private nosave object *pending, *players, *now_playing, *played;
private nosave object cur_plr, dealer;
private nosave int hand, doing_split, max_players, min_bet, max_bet, deck_amt;
private nosave int main_id, reset_id, check_id;
private nosave string *deck, savefile, dealer_file, place;
private nosave mapping data, used;

class player_data {
    string *hand1;
    string *hand2;
    int *bets;
}

int winnings_paid, raw_income;

private void reset_game();
varargs void hands( object player, int which );
varargs void check_state( int kick );
varargs mixed eval_cards( object player, int hand_b, int text );
void switch_player();
void deal_card( object plr );
void make_dealer();
int do_bet( object *monies );

/** @ignore yes */
string *query_deck() { return deck; }

/** @ignore yes */
mapping query_used() { return used; }

/** @ignore yes */
mapping query_data() { return data; }

/** @ignore yes */
object *query_players() { return players; }

/** @ignore yes */
private void get_play_data() {
    if( savefile && savefile != "" && file_exists( savefile ) )
        unguarded( (: restore_object, savefile :) );
} /* get_play_data() */

/** @ignore yes */
private void save_play_data() {
    unguarded( (: save_object,savefile :) );
} /* save_play_data() */

/** @ignore yes */
void create() {
    ::create();

    get_play_data();

    players = ({ });
    played = ({ });
    data = ([ ]);
    now_playing = ({ });
    deck = ({ });
    pending = ({ });

    add_help_file("blackjack_table");

    if( base_name(TO) != __FILE__[0..<3] ) {
        if( !savefile )
            write("Save file unset for "+base_name(TO)+".\n");
        make_dealer();
    }

    add_extra_look(TO);

} /* create() */

/** @ignore yes */
void reset() {
    if( !dealer && base_name(TO) != __FILE__[0..<3] )
        call_out("make_dealer", 4 );
} /* reset() */

/**
 * This method creates a generic dealer for the blackjack room.
 * If you want to have a custom dealer, please overwrite this
 * function in the actual room.  Alternatively you can specify
 * a dealer to be loaded from a separate file.
 * @see set_dealer()
 * @see query_dealer()
 */
void make_dealer() {
        if( base_name(TO) == __FILE__[0..<3] )
            return;

    if( !dealer ) {
        if( !dealer_file ) {
            dealer = clone_object(NPC_OBJ);
            dealer->set_name("dealer");
            dealer->set_short("Speedy the dealer");
            dealer->set_determinate("");
            dealer->add_alias( ({"speedy", "Speedy", "dealer"}) );
            dealer->add_adjective( ({"Speedy", "speedy", "the"}) );
            dealer->add_property("unique", 1 );

            dealer->set_long("Speedy is a short man with ragged clothes full "
                "of holes.  His composure reminds that of an old woman, and "
                "it is hard to see whether he is standing or sitting.\n");

            dealer->basic_setup("human", 200 + random( 50 ), 1 );

            dealer->load_chat( 20, ({
                1, ":lights a cigarette and blows a big cloud of smoke into "
                   "the air.",
                1, ":sighs.",
                1, ":gets a nasty look in his eyes, then coughs so loudly it "
                   "would scare off most people."
                }) );

            dealer->load_a_chat( 50, ({
                1, "'You will regret this!",
                1, "'Come on, leave me alone!",
                1, "'I've done nothing wrong, have I?!",
                }) );
        } else {
            dealer = clone_object(dealer_file);
        }
    }

    if( !ENV(dealer) || ENV(dealer) != TO )
        dealer->move( TO, "$N enter$s from elsewhere.",
                          "$N run$s away, screaming.");

} /* make_dealer() */

/**
 * This method specifies the optional external dealer file to clone
 * the dealer from.  It is usually better to overwrite make_dealer()
 * in the actual room though.
 * @param word the file name of the dealer to load
 */
void set_dealer( string word ) {
    if( word && word != "" ) {
        if( word[<2..] == ".c" )
            word = word[0..<3];
        if( file_exists( word+".c" ) ) {
            dealer_file = word;
            return;
        }
    }

    error("Trying to set an invalid dealer file - "+word+"\n");

} /* set_dealer() */

/**
 * This method returns the dealer file (if one is set).
 * @return the external dealer file used
 */
string query_dealer() { return dealer_file; }

/**
 * This method sets the save file for the blackjack table.
 * That file will be used to save the profit made etc.
 * @param word the save file to use
 */
void set_save_file( string word ) {
    savefile = word+".o";
} /* set_save_file() */

/**
 * This method returns the save file of the blackjack table.
 * @return the save file path and name
 */
string query_save_file() { return savefile; }

/**
 * This method sets the minimum possible bet in this table.
 * @param amount the minimum bet amount
 */
void set_min_bet( int amount ) {
    min_bet = amount;
} /* set_min_bet() */

/**
 * This method returns the minimum possible bet in this table.
 * @return the minimum bet amount
 */
int query_min_bet() { return min_bet; }

/**
 * This method sets the maximum possible bet in this table.
 * @param amount the maximum bet amount
 */
void set_max_bet( int amount ){
    max_bet = amount;
} /* set_max_bet() */

/**
 * This method returns maximum possible bet in this table.
 * @return the maximum bet amount
 */
int query_max_bet() { return max_bet; }

/**
 * This method sets the maximum number of players possible in this table.
 * @param amount the maximum number of players
 */
void set_max_players( int amount ) {
    max_players = amount;
} /* set_max_players() */

/**
 * This method returns the maximum number of players possible in this table.
 * @return the maximum number of players
 */
int query_max_players() { return max_players; }

/**
 * This method sets the number of decks to use in the blackjack game.
 * Normally this should be set to 6.
 * @param amount the number of decks to use
 */
void set_deck_amount( int amount ) {
    deck_amt = amount;
} /* set_deck_amt() */

/**
 * This method returns the number of decks to use in the blackjack game.
 * @return the number of decks to use
 */
int query_deck_amount() { return deck_amt; }

/**
 * This method sets the place the blackjack table operates in.
 * It is needed when the table is outside of the "default" money area.
 * @param word the place to set
 */
void set_place( string word ) { place = word; }

/**
 * This method returns the place this blackjack table operates in.
 * @return the place we're operating in
 */
string query_place() { return place || "default"; }

/** @ignore yes */
void init() {
    ::init();
    add_command("join", "");
    add_command("join", "game");
    add_command("bet", "<indirect:object:me>'money'", (: do_bet($1) :) );
    add_command("hit", "");
    add_command("double", "");
    add_command("split", "");
    add_command("stand", "");
    add_command("cards", "");
    add_command("leave", "");
    add_command("status", "");
    add_command("count", "cards");
    if( creatorp(TP) )
        add_command("statistics", "");
} /* init() */

/**
 * This method returns 1 if the specified player is already playing the game.
 * @param player the player to check
 * @return 1 if the player is already playing the game
 */
int query_playing( object player ) {
        return member_array( player, players ) != -1;
} /* query_playing() */

/**
 * This method returns 1 if the specified player in the pending array,
 * waiting to start playing.
 * @param player the player to check
 * @return 1 if the player is pending
 */
int query_pending( object player ) {
        return member_array( player, pending ) != -1;
} /* query_playing() */

/** @ignore yes */
int do_join() {
    if( !dealer || ENV(dealer) != TO ) {
        add_failed_mess("The dealer isn't present at the moment.\n");
        return 0;
    }

    if( sizeof(pending) + sizeof(players) >= max_players ) {
        add_failed_mess("The table is already full.  Please wait until "
            "someone leaves the game.\n");
        return 0;
    }

    if( query_pending( TP ) ) {
        add_failed_mess("You are already waiting for the next round to "
            "start.\n");
        return 0;
    }

    if( query_playing( TP ) ) {
        add_failed_mess("You are already playing the game.\n");
        return 0;
    }

    pending += ({ TP });
    add_succeeded_mess("$N sit$s on one of the seats and join$s the game.\n");

    if( !hand ) {
        if( reset_id )
            remove_call_out( reset_id );
        reset_id = call_out( (: reset_game :), 10 );
    }

    return 1;

} /* do_join */

/** @ignore yes */
void end_game() {
    hand = 0;
    doing_split = 0;
    cur_plr = 0;
    data = ([ ]);
    played = ({ });
    now_playing = ({ });

    if( main_id ) {
        remove_call_out( main_id );
        main_id = 0;
    }

    if( check_id ) {
        remove_call_out( check_id );
        check_id = 0;
    }

    if( reset_id )
        remove_call_out( reset_id );

    reset_id = call_out( (: reset_game :), 10 );

} /* end_game() */

/** @ignore yes */
void remove_player( object plr ) {
    map_delete( data, plr );
    now_playing -= ({ plr });
    players -= ({ plr });
    pending -= ({ plr });
    played -= ({ plr });
} /* remove_player() */

/** @ignore yes */
int do_leave() {
    if( classp( data[TP] ) && data[TP]->bets ) {
        add_failed_mess("You have placed bets this round, please wait "
            "until the game is finished.\n");
        return 0;
    }

    if( query_pending( TP ) || query_playing( TP ) ) {
        remove_player( TP );
        if( !sizeof(pending) && !sizeof(players) ) {
            add_succeeded_mess("$N $V the blackjack table and $I clears the "
                "table as there is no-one left playing.\n", ({ dealer }) );
            end_game();
            return 1;
        }
        add_succeeded_mess("$N $V the blackjack table.\n");
        return 1;
    }

    add_failed_mess("You are not playing blackjack.\n");
    return 0;

} /* do_leave() */

/** @ignore yes */
int do_bet( object *money ) {
    int value;
    object *tender, legal, illegal, cont;
    class player_data game_data;

    if( !query_playing( TP ) ) {
        if( query_pending( TP ) ) {
            add_failed_mess("You have to wait until a new round begins to "
                "place your bet.\n");
            return 0;
        }
        add_failed_mess("You are not playing blackjack.  Please join the "
            "game first.\n");
        return 0;
    }

    if( !hand ) {
        add_failed_mess("You have to wait until a new round begins to "
            "place your bet.\n");
        return 0;
    }

    game_data = data[TP] || new( class player_data );

    if( game_data->bets || member_array( TP, played ) != -1 ) {
        add_failed_mess("You have already placed your bet this round.\n");
        return 0;
    }

    if( sizeof( filter( money, (: !$1->query_property( "money" ) :) ) ) ) {
        add_failed_mess("You can only bet money, silly.\n");
        return 0;
    }

    cont = clone_object("/std/container");
    money->move( cont );
    money = INV( cont );

    if( !sizeof( money ) ) {
        cont->dest_me();
        add_failed_mess("Something is broken, please contact a creator.\n");
        return 0;
    }

    tender = MONEY_H->filter_legal_tender( money[ 0 ], query_place() );
    legal = tender[ 0 ];
    illegal = tender[ 1 ];
    cont->dest_me();

    if( illegal ) {
        tell_object( TP, "Sorry, "+(string)MONEY_H->money_string(
            illegal->query_money_array() )+" is not legal money here.\n"
            "Betting "+(string)MONEY_H->money_string(
            legal->query_money_array() )+".\n");
        illegal->move( TP );
    }

    if( legal ) {
        value = legal->query_value_in(query_place());
        if( query_place() != "default" )
            value += legal->query_value_in("default");

        if( !value || value > max_bet || value < min_bet ) {
            legal->move(TP);
            add_failed_mess("The bet has to be between "+
                MSTRING(min_bet)+" and "+MSTRING(max_bet)+".\n");
            return 0;
        }

        game_data->bets = ({ value });
        data[TP] = game_data;
        raw_income += value;

        tell_object( TP, "You bet "+(string)MONEY_H->money_string(
            legal->query_money_array() )+".\n");
        tell_room( TO, TP->the_short()+" "+verbalize("bet")+" "+
            (string)MONEY_H->money_string( legal->query_money_array() )+
            ".\n", TP );

        legal->move("/room/rubbish");
        add_succeeded_mess("");
        return 1;
    }

    add_failed_mess("Unable to find any legal money to bet, sorry.\n");
    return 0;

} /* do_bet() */

/** @ignore yes */
void shuffle_deck() {
    int i, j;
    string card, tmp, *all;

    all = ({ });
    deck = ({ });
    used = ([ ]);

    // Assemble a single deck.
    foreach( card in keys(TYPES) )
        foreach( tmp in CARDS )
            all += ({ card + tmp });

    // This is a deck of many decks.
    for( i = 0; i < deck_amt; i++ )
            all += all;

    for( i = 0; i < deck_amt * 52; i++ ) {
        j = random(sizeof(all));
        deck += all[j..j];
        all = all[0..j-1] + all[j+1..];
    }

    tell_room( TO, dealer->the_short()+" produces "+
        query_num(deck_amt)+" new deck"+( deck_amt > 1 ? "s" : "")+" and "
        "shuffles the cards.\n");

} /* shuffle_deck() */

/** @ignore yes */
private void reset_game() {
    players = filter( players,(: $1 && interactive($1) :) );
    players += filter( pending, (: $1 && interactive($1) :) );

    if( !sizeof(players) ) {
        hand = 0;
        return;
    }

    hand = 1;
    pending = ({ });

    if( sizeof(deck) < 52 )
        shuffle_deck();

    dealer->do_command("\"Players, place your bets please.");

    if( !check_id )
        check_id = call_out( (: check_state :), 20 );

} /* reset_game() */

/** @ignore yes */
string get_name( int val ) {
    switch( val ) {
      case 2..10 :
        return CAP( query_num( val ) );
      default:
        return NAMES[val];
    }
} /* get_name() */

/** @ignore yes */
string card_name( string card ) {
    return add_a( get_name( to_int(card[1..2]) ) )+" of "+TYPES[card[0..0]];
} /* card_name() */

/** @ignore yes */
string *query_hand( object player, int i ) {
    if( !classp( data[player] ) )
        return ({ });

    if( i == 1 && data[player]->hand1 )
        return map( data[player]->hand1, (: card_name($1) :) );

    if( i == 2 && data[player]->hand2 )
        return map( data[player]->hand2, (: card_name($1) :) );

    return ({ });

} /* query_hand() */

/** @ignore yes */
string count_helper( int num ) {
    switch( num ) {
      case 0..2  : return "a couple";
      case 3..5  : return "a few";
      case 6..10 : return "several";
      default    : return "a bunch";
    }
} /* count_helper() */

/** @ignore yes */
int do_count() {
    string ret, name;

    if( !sizeof( used ) ) {
        add_failed_mess("No cards from this deck have been used yet.\n");
        return 0;
    }

    ret = "The following cards have been used from the currently used "
          "deck:\n";

    foreach( name in sort_array( keys(used), -1 ) )
        ret +=  sprintf("    %*'.'-s ", 18, get_name( to_int(name) ) )+
                count_helper( used[name] )+"\n";

    add_succeeded_mess("");
    tell_object( TP, ret );
    return 1;

} /* do_count() */

/** @ignore yes */
int do_status() {
    string ret;
    object player, *obs;
    int width;

    add_succeeded_mess("");

    if( !sizeof(players) && !sizeof(pending) ) {
        tell_object( TP, "No-one is playing blackjack right now.\n");
        return 1;
    }

    if( !hand ) {
        tell_object( TP, "There isn't a round going on at the moment.\n");
        return 1;
    }

    width = (int)TP->query_cols();

    ret = sprintf("%|*'-'s\n", width-1, "=========] Game Status [==========");

    if( sizeof(now_playing) ) {
        obs = ( member_array( TP, now_playing ) != -1 ? ({ TP }) : ({ }) );
        obs += now_playing - ({ TP });
        foreach( player in obs ) {
            ret += "$C$"+player->poss_short()+" first hand cards: "+
                   query_multiple_short( query_hand( player, 1 ) )+"\n";
            if( classp( data[player] ) && sizeof( data[player]->hand2 ) )
                ret += "$C$"+player->poss_short()+" second hand cards: "+
                   query_multiple_short( query_hand( player, 2 ) )+"\n";
            ret += "\n";
        }
        ret += "$C$"+dealer->poss_short()+" cards: "+
               query_multiple_short( query_hand( dealer, 1 ) )+"\n";
        if( objectp(cur_plr) )
            ret += sprintf("%*'-'|s\n", width-1, "> It is currently "+
                TP->convert_message( cur_plr->poss_short() )+" turn. <");
        tell_object( TP, ret );
        return 1;
    }

    tell_object( TP, "None of the players have any cards yet.\n");
    return 1;

} /* do_status() */

/** @ignore yes */
void do_say( mixed who, string message ) {
    dealer->do_command("sayto "+( pointerp(who) ?
        implode( map( who, (: $1->query_name() :) ), "," ) :
        who->query_name() )+" "+message );
} /* do_say() */

/** @ignore yes */
void pay_out( object player, int hnd ) {
    int tmp_bet, bet, eval;

    switch( hnd ) {
      case 0:
        eval = eval_cards( player, 1 );
        if( eval == BLACKJACK ) {
            bet = data[player]->bets[0];
            tmp_bet = bet * 3 / 2;
        } else {
            foreach( bet in data[player]->bets )
                tmp_bet += bet;
            bet = tmp_bet;
        }
        player->adjust_money( MARRAY( tmp_bet + bet ) );
        winnings_paid += tmp_bet;
        tell_object( player, "You receive "+MSTRING( tmp_bet )+" in "
            "winnings"+( eval == BLACKJACK ? " for the blackjack, plus your "
            "original bet" : ", plus the original bet for your cards")+".\n");
        tell_room( TO, player->the_short()+" "+verbalize(
            "receive winnings for their", "receives winnings for "+
            player->HIS )+" cards.\n", player );
        return save_play_data();
      case 1:
        bet = data[player]->bets[0];
        eval = eval_cards( player, 1 );
        tmp_bet = ( eval == BLACKJACK ? bet * 3 / 2 : bet );
        player->adjust_money( MARRAY( tmp_bet + bet ) );
        winnings_paid += tmp_bet;
        tell_object( player, "You receive "+MSTRING( tmp_bet )+" in "
            "winnings"+( eval == BLACKJACK ? " for the blackjack, plus your "
            "original bet" : ", plus the original bet for your first hand")+
            ".\n");
        tell_room( TO, player->the_short()+" "+verbalize("receive winnings "
            "for their", "receives winnings for "+player->HIS )+" first hand "
            "cards.\n", player );
        return save_play_data();
      case 2:
        bet = data[player]->bets[0];
        player->adjust_money( MARRAY( bet * 2 ) );
        winnings_paid += bet;
        tell_object( player, "You receive "+MSTRING( bet )+" in "
            "winnings, plus the original bet for your second hand.\n");
        tell_room( TO, player->the_short()+" "+verbalize("receive")+" "+
            "winnings for "+verbalize("their", player->HIS )+" second "+
            "hand cards.\n", player );
        return save_play_data();
    default:
        tell_object( player, "Something went wrong in paying out the winnings, "
            "please contact a creator immediately or send in a bug "
            "report.\n");
    }

} /* pay_out() */

/** @ignore yes */
void black_jack( object player ) {
    tell_room( TO, player->the_short()+" got a blackjack!\n");
    pay_out( player, 0 );
    map_delete( data, player );
    now_playing -= ({ player });
    cur_plr = 0;
} /* black_jack() */

/** @ignore yes */
void start_game() {
    object player;
    int eval;

    now_playing = filter( keys(data), (: $1 && interactive($1) :) );

    foreach( player in now_playing )
        deal_card(player);

    data[dealer] = new( class player_data );
    deal_card(dealer);

    foreach( player in now_playing )
        deal_card(player);

    eval = eval_cards( dealer, 1 );

    if( eval > 9 && eval < 12 ) {
        tell_room( TO, dealer->the_short()+" has a possibility for a "
             "blackjack so no blackjacks are paid out at this point.\n");
        return switch_player();
    }

    foreach( player in now_playing )
        if( eval_cards( player, 1 ) == BLACKJACK )
            black_jack(player);

    switch_player();

} /* start_game() */

/** @ignore yes */
varargs void check_state( int kick ) {
    object *obs;

    if( check_id )
        remove_call_out( check_id );

    check_id = 0;

    if( !sizeof(pending) && !sizeof(players) )
        return end_game();

    obs = filter( players, (: $1 && undefinedp( data[$1] ) :) );

    if( sizeof(obs) ) {
        if( kick > 5 && ( sizeof(pending) || sizeof( data ) ) ) {
            do_say( obs, "I'm afraid I am going to have to remove you "
                "from the game because we need to continue.");
            players -= obs;
        } else {
            if( kick > 10 ) {
                tell_room( TO, dealer->the_short()+" calls off the round "
                    "because none of the players are responding.\n");
                players = ({ });
                return end_game();
            }
            do_say( obs, "Please place your bet"+( sizeof(obs) > 1 ?
                "s" : "")+" so that we can continue with the game.");
            check_id = call_out( (: check_state :), 20, ++kick );
            return;
        }
    }

    if( sizeof(players) )
        return start_game();

    return end_game();

} /* check_state() */

/** @ignore yes */
private void end_it_all() {
    dealer = 0;
    players = ({ });
    pending = ({ });
    end_game();
} /* end_it_all() */

/** @ignore yes */
void event_death( object thing, object *people, object killer,
                  string room_mess, string killer_mess ) {
    if( !objectp(thing) )
        return;

    if( thing == dealer ) {
        if( sizeof(pending) || sizeof(players) ) {
            if( objectp(killer) ) {
                tell_object( killer, "You have killed the dealer and the "
                    "game is interrupted.\n");
                tell_room( TO, "Oh no!  "+killer->the_short()+" has killed "+
                    dealer->the_short()+" and the game is interrupted.\n",
                    killer );
            } else {
                tell_room( TO, "Oh no!  Someone has killed "+
                    dealer->the_short()+" and the game is interrupted.\n");
            }
        }
        return end_it_all();
    }

} /* event_death() */

/** @ignore yes */
void event_exit( object ob, string mess, object to ) {
    ::event_exit();

    if( ob == dealer ) {
        if( sizeof(pending) || sizeof(players) )
            tell_room( TO, dealer->the_short()+" leaves the room and the "
                "game is interrupted.\n");
        return end_it_all();
    }

    if( query_pending( ob ) || query_playing( ob ) ) {
        tell_object( ob, "You leave the blackjack table.\n");
        tell_room( TO, ob->one_short()+" "+verbalize("leave")+" the "
            "blackjack table.\n", ob );

        remove_player(ob);

        if( ob == cur_plr )
            return switch_player();

        if( !sizeof(players) )
            return end_game();
    }

} /* event_exit() */

/** @ignore yes */
int card_val( string card ) {
    int val;
    
    val = to_int( card[1..] );

    switch( val ) {
      case 2..9:
        return val;
      case 10..13:
        return 10;
      case 14:
        return 11;
      default:
        return 0;
    }

} /* card_val() */

/** @ignore yes */
varargs int split_ok( object player, int flag ) {
    if( !data[player]->hand1 ) {
        if( flag )
            add_failed_mess("You can't split your hand because you don't "
                "appear to have any cards.\n");
        return 0;
    }

    if( sizeof( data[player]->hand1 ) > 2 ) {
        if( flag )
            add_failed_mess("You can only split a hand of two equal "
                "cards.\n");
        return 0;
    }

    if( data[player]->hand2 ) {
        if( flag )
            add_failed_mess("You have already split or doubled your hand "
                "this round.\n");
        return 0;
    }

    if( card_val( data[player]->hand1[0] ) ==
        card_val( data[player]->hand1[1] ) )
        return 1;

    if( flag )
        add_failed_mess("You can only split a hand of two equal cards.\n");

    return 0;

} /* split_ok() */

/** @ignore yes */
varargs int double_ok( object player, int flag ) {
    int eval;

    if( !data[player]->hand1 ) {
        if( flag )
            add_failed_mess("You can't double your hand because you don't "
                "appear to have any cards.\n");
        return 0;
    }

    if( sizeof( data[player]->hand1 ) > 2 ) {
        if( flag )
            add_failed_mess("You cannot double your hand unless you have "
                "two cards only, and the sum of their values is ten or "
                "eleven.\n");
        return 0;
    }

    if( data[player]->hand2 ) {
        if( flag )
            add_failed_mess("You have already split or doubled your hand "
                "this round.\n");
        return 0;
    }

    eval = eval_cards( player, 1 );

    if( eval > 9 && eval < 12 )
        return 1;

    if( flag )
        add_failed_mess("You cannot double your hand unless you have "
            "two cards only, and the sum of their values is ten or "
            "eleven.\n");

    return 0;

} /* double_ok() */

/** @ignore yes */
void do_announce( object player, int flag ) {
    string ret;

    if( flag ) {
        do_say( player, "You can either hit or stand.  Please choose one.");
        return;
    }

    if( split_ok(player) && double_ok(player) )
        ret = ", stand, split or double";

    if( split_ok(player) && !double_ok(player) )
        ret = ", stand or split";

    if( !split_ok(player) && !double_ok(player) )
        ret = " or stand";

    if( !split_ok(player) && double_ok(player) )
        ret = ", stand or double";

    do_say( player, "You can either hit"+ret+".  Please choose one.");

} /* do_announce() */

/** @ignore yes */
varargs void main_game( object player, int kick ) {
    if( kick > 1 && ( sizeof(players) + sizeof(pending) ) > 1 ) {
        do_say( player, "If you're here to idle, then be my guest, but I am "
            "going to have to remove you from the game.");
        remove_player(player);
        return switch_player();
    }

    if( kick > 6 ) {
        do_say( player, "I don't have the patience to wait after you "
            "forever, so I am going to remove you from the game.");
        remove_player(player);
        return switch_player();
    }

    do_announce( player, 0 );
    main_id = call_out( (: main_game :), 30, player, ++kick );

} /* main_game() */

/** @ignore yes */
int check_player() {
    if( query_pending(TP) ) {
        add_failed_mess("You are waiting for a new round to begin.\n");
        return 0;
    }

    if( !query_playing(TP) ) {
        add_failed_mess("You are not playing blackjack.  Please join the "
            "game first.\n");
        return 0;
    }

    if( !hand ) {
        add_failed_mess("You have to wait for a new round to begin.\n");
        return 0;
    }

    if( !sizeof(data) || !data[TP] ) {
        add_failed_mess("You have not placed a bet yet.\n");
        return 0;
    }

    if( member_array( TP, now_playing ) == -1 ) {
        add_failed_mess("You don't have any cards yet.\n");
        return 0;
    }

    if( TP != cur_plr ) {
        add_failed_mess("It is not your turn right now.\n");
        return 0;
    }

    return 1;

} /* check_player() */

/** @ignore yes */
int do_split() {
    if( !check_player() )
        return 0;

    if( split_ok( TP, 1 ) ) {
        if( (int)TP->query_value_in( query_place() ) >= data[TP]->bets[0] ) {
            data[TP]->hand2 = ({ data[TP]->hand1[1] });
            data[TP]->hand1 = ({ data[TP]->hand1[0] });

            hand = 1;
            doing_split = 1;

            data[TP]->bets += ({ data[TP]->bets[0] });
            TP->pay_money( MARRAY( data[TP]->bets[0] ) );

            add_succeeded_mess("");

            tell_object( TP, "You decide to split your hand and double the "
                "bet, tossing the money onto the table with a tinkle.\n");
            tell_room( TO, TP->the_short()+" decides to split "+
                TP->HIS+" hand and double the bet, tossing the money onto "
                "the table with a tinkle.\n", TP );

            do_say( TP, "Let's start with your first hand.");
            hands( TP, 1 );
            if( main_id ) {
                remove_call_out( main_id );
                main_id = 0;
            }
            main_game( TP );
            return 1;
        }

        add_failed_mess("You do not have enough money to split your hand.\n");
        return 0;
    }

    return 0;

} /* do_split() */

/* @ignore yes */
int do_double() {
    if( !check_player() )
        return 0;

    if( doing_split ) {
        add_failed_mess("You cannot double a splitted hand.\n");
        return 0;
    }

    if( double_ok( TP, 1 ) ) {
        if( (int)TP->query_value_in( query_place() ) >= data[TP]->bets[0] ) {
            TP->pay_money( MARRAY( data[TP]->bets[0] ) );

            add_succeeded_mess("");
            tell_object( TP, "You double your bet, tossing the money onto "
                "the table with a tinkle, and receive one more card.\n");
            tell_room( TO, TP->the_short()+" doubles "+TP->HIS+" bet, "
                "tossing the money onto the table with a tinkle, and "
                "receives one more card.\n", TP );

            deal_card(TP);
            data[TP]->bets += ({ data[TP]->bets[0] });

            if( eval_cards( TP, 1 ) == BUSTED ) {
                do_say( TP, "You're busted, better luck next round.");
                data[TP]->bets = ({ });
            }

            switch_player();
            return 1;
        }

        add_failed_mess("You do not have enough money to double your hand.\n");
        return 0;
    }

    return 0;

} /* do_double() */

/** @ignore yes */
int do_stand() {
    if( !check_player() )
        return 0;

    add_succeeded_mess("");

    if( doing_split ) {
        if( hand == 1 ) {
            tell_object( TP, "You are satisfied with your first hand.\n");
            tell_room( TO, TP->the_short()+" is satisfied with "+
                TP->HIS+" first hand.\n", TP );
            do_say( TP, "Let's have a look at your second hand now.");
            hand = 2;
            hands( TP, 2 );
            do_announce( TP, 0 );
            if( main_id )
                remove_call_out( main_id );
            main_id = call_out( (: main_game :), 30, TP );
            return 1;
        } else if( hand == 2 ) {
            tell_object( TP, "You are satisfied with your second hand.  "
                "Please wait until the round is finished.\n");
            tell_room( TO, TP->the_short()+" is satisfied with "+
                TP->HIS+" second hand.\n", TP );
            hand = 1;
            doing_split = 0;
            switch_player();
            return 1;
        }
        add_failed_mess("Something has gone wrong, please contact a creator "
            "immediately or file a bug report.\n");
        return 0;
    }

    tell_object( TP, "You are satisfied with your cards.  Please wait until "
        "the round is finished.\n");
    tell_room( TO, TP->the_short()+" is satisfied with "+
        TP->HIS+" cards.\n", TP );

    switch_player();
    return 1;

} /* do_stand */

/** @ignore yes */
varargs void hands( object player, int which ) {
    string ret, *handt, tmp;
    int aces, i, val;

    if( !sizeof(data) || !data[player] ||
        ( !data[player]->hand1 && !data[player]->hand2 ) ) {
        tell_object( player, "You don't have any cards at the moment.\n");
        return;
    }

    ret = "";

    if( ( handt = data[player]->hand1 ) && ( !which || which == 1 ) ) {
        if( !which )
            ret += "You have the following cards:\n";
        ret += "Hand one: "+query_multiple_short( map( handt,
               (: card_name($1) :) ) )+".\n";
        ret += "Value of the hand: %^WHITE%^";

        aces = 0;
        val = 0;

        foreach( tmp in handt ) {
            val += ( i = card_val( tmp ) );
            if( i == 11 )
                aces++;
        }

        for( i = 0; i < aces; i++ ) {
            if( val > 21 )
                val -= 10;
            else if( i < aces ) {
                i = eval_cards( player, 1 );
                if( i != BLACKJACK && i != BUSTED )
                    ret += CAP( query_num(i-10) ) + " / ";
                break;
            }
        }
        ret += eval_cards( player, 1, 1 )+"%^RESET%^";
        if( which == 1 ) {
            tell_object( player, ret+"\n");
            return;
        }
    }

    if( ( handt = data[player]->hand2 ) && ( !which || which == 2 ) ) {
        if( !which )
            ret += "\n";
        ret += "Hand two: "+
               query_multiple_short( map( handt, (: card_name($1) :) ) )+".\n";
        ret += "Value of the hand: %^WHITE%^";

        aces = 0;
        val = 0;

        foreach( tmp in handt ) {
            val += ( i = card_val( tmp ) );
            if( i == 11 )
                aces++;
        }

        for( i = 0; i < aces; i++ ) {
            if( val > 21 )
                val -= 10;
            else if( i < aces ) {
                i = eval_cards( player, 2 );
                if( i != BLACKJACK && i != BUSTED )
                    ret += CAP( query_num(i-10) ) + " / ";
                break;
            }
        }
        ret += eval_cards( player, 2, 1 )+"%^RESET%^";
        if( which == 2 ) {
            tell_object( player, ret+"\n");
            return;
        }
    }

    if( ret == "" )
        ret += "You don't have any cards at the moment.\n";

    tell_object( player, ret+"\n");

} /* hands() */

/** @ignore yes */
int do_cards() {
    if( !query_playing(TP) && !query_pending(TP) ) {
        add_failed_mess("You are not playing blackjack and don't have "
            "any cards.\n");
        return 0;
    }

    hands(TP);
    add_succeeded_mess("");
    return 1;

} /* do_cards() */

/** @ignore yes */
int do_hit() {
    int eval;

    if( !check_player() )
        return 0;

    tell_object( TP, "You request another card from "+dealer->the_short()+".\n");
    tell_room( TO, TP->one_short()+" requests another card from "+
        dealer->the_short()+".\n", TP );

    add_succeeded_mess("");
    deal_card(TP);

    eval = eval_cards( TP, hand );

    if( doing_split ) {
        if( eval == BUSTED && hand == 1 ) {
            hand = 2;
            do_say( TP, "You have busted your first hand.  Let's have a look "
                "at your second hand now.");
            hands( TP, 2 );
            data[TP]->bets = ({ data[TP]->bets[0] });
            do_announce( TP, 1 );
            if( main_id )
                remove_call_out( main_id );
            main_id = call_out( (: main_game :), 30, TP );
            return 1;
        }

        if( eval == BUSTED && hand == 2 ) {
            do_say( TP, "Your second hand is busted, better luck next "
                "round.");
            if( sizeof( data[TP]->bets ) > 1 )
                data[TP]->bets = ({ data[TP]->bets[0] });
            else
                map_delete( data, TP );
            hand = 1;
            doing_split = 0;
            switch_player();
            return 1;
        }

        if( eval > 0 && eval < 22 ) {
            hands( TP, hand );
            do_announce( TP, 1 );
            if( main_id )
                remove_call_out( main_id );
            main_id = call_out( (: main_game :), 30, TP );
            return 1;
        }

        add_failed_mess("Something has gone wrong, please contact a creator "
            "immediately, or file a bug report.\n");
        return 0;
    }

    if( eval == BUSTED ) {
        do_say( TP, "You're busted, better luck next round.");

        if( sizeof(now_playing) == 1 ) {
            tell_room( TO, "All players are busted and "+
                dealer->the_short()+" clears the table.\n");
            end_game();
            return 1;
        }

        map_delete( data, TP );
        switch_player();
        return 1;
    }

    hands(TP);
    do_announce( TP, 1 );
    if( main_id )
        remove_call_out( main_id );
    main_id = call_out( (: main_game :), 30, TP );
    return 1;

} /* do_hit() */

/** @ignore yes */
void deal_card( object player ) {
    string *cards;

    if( !sizeof(deck) )
        shuffle_deck();

    if( hand == 1 ) {
        cards = data[player]->hand1 || ({ });
        cards += ({ deck[0] });
        data[player]->hand1 = cards;
    }

    if( hand == 2 ) {
        cards = data[player]->hand2 || ({ });
        cards += ({ deck[0] });
        data[player]->hand2 = cards;
    }

    tell_room( TO, dealer->the_short()+" deals "+( player == dealer ?
        dealer->HIM+"self" : player->one_short() )+" "+
        card_name( deck[0] )+".\n");

    used[ deck[0][1..] ]++;
    deck -= ({ deck[0] });

} /* deal_card() */

/** @ignore yes */
varargs mixed eval_cards( object player, int hand_b, int text ) {
    string *cards, card;
    int i, aces, val;

    if( hand_b == 1 )
        cards = data[player]->hand1;
    if( hand_b == 2 )
        cards = data[player]->hand2;

    foreach( card in cards ) {
        val += ( i = card_val( card ) );
        if( i == 11 )
            aces++;
    }

    if( aces ) {
        for( i = 0; i < aces; i++ ) {
            if( val > 21 )
                val -= 10;
        }
    }

    if( text ) {
        if( val > 21 )
            return "Busted";
        if( val == 21 && sizeof( data[player]->hand1 ) < 3 &&
            !data[player]->hand2 )
            return "Blackjack";
        return CAP( query_num( val ) );
    }

    if( val > 21 )
        return BUSTED;

    if( val == 21 && sizeof( data[player]->hand1 ) < 3 &&
        !data[player]->hand2 )
        return BLACKJACK;

    return val;

} /* eval_cards() */

/** @ignore yes */
void settle_game() {
    int pval, dval;
    string money;
    object player;
    class player_data game_data;

    if( main_id ) {
        remove_call_out( main_id );
        main_id = 0;
    }

    dval = eval_cards( dealer, 1 );

    if( dval == BLACKJACK ) {
        tell_room( TO, dealer->the_short()+" got a blackjack and all bets "
            "are lost.\n");
        return end_game();
    }

    foreach( player, game_data in data ) {
        if( player == dealer )
            continue;

        pval = game_data->bets[0];
        if( sizeof(game_data->bets) == 2 )
            pval += game_data->bets[1];

        money = MSTRING( pval );

        if( game_data->hand1 ) {
            pval = eval_cards( player, 1 );
            switch( pval ) {
              case BUSTED:
                tell_object( player, "Your"+( game_data->hand2 ? " first" :
                    "")+" hand was busted.\n");
                break;
              case BLACKJACK:
                black_jack(player);
                break;
              default:
                if( pval > dval ) {
                    tell_object( player, "Your"+( game_data->hand2 ? " first" :
                        "")+" hand with a value of "+query_num(pval)+" beats "+
                        dealer->poss_short()+" first hand, which has a value "
                        "of "+query_num(dval)+".\n");
                    pay_out( player, !game_data->hand2 ? 0 : 1 );
                    break;
                }
                tell_object( player, "Your"+( game_data->hand2 ? " first" :
                    "")+" hand "+( pval == dval ? "with a value of "+
                    query_num(pval)+" is tied with "+
                    dealer->poss_short()+" cards" : "is valued at "+
                    query_num(pval)+" and "+dealer->poss_short()+" first "
                    "hand is valued at "+query_num(dval) )+".  You lose your "
                    "bet of "+money+".\n");
                tell_room( TO, player->the_short()+" "+verbalize("lose their "
                    "bets for their", "loses "+player->HIS+" bet for "+
                    player->HIS )+" "+( game_data->hand2 ? "first hand" : "")+
                    "cards.\n", player );
            }
        }

        if( game_data->hand2 ) {
            pval = eval_cards( player, 2 );
            switch( pval ) {
              case BUSTED:
                tell_object( player, "Your second hand was busted.\n");
              break;
              default:
                if( pval > dval ) {
                    tell_object( player, "Your second hand with a value of "+
                        query_num(pval)+" beats "+dealer->poss_short()+" second "
                        "hand, which has a value of "+query_num(dval)+".\n");
                    pay_out( player, 2 );
                    break;
                }
                tell_object( player, "Your second hand "+( pval == dval ?
                    "with a value of "+query_num(pval)+" is tied with "+
                    dealer->poss_short()+" cards" : "is valued at "+
                    query_num(pval)+" and "+dealer->poss_short()+" first "
                    "hand is valued at "+query_num(dval) )+".  You lose your "
                    "bet of "+money+".\n");
                tell_room( TO, player->the_short()+" "+verbalize("lose their "
                    "bets for their", "loses "+player->HIS+" bet for "+
                    player->HIS )+" second hand cards.\n", player );
            }
        }
    }

    tell_room( TO, dealer->the_short()+" settles the game collecting all "
        "cards.\n");
    end_game();

} /* settle_game() */

/** @ignore yes */
private void dealer_game() {
    int tmp_val, aces, i;
    string card;
    object player;

    tmp_val = eval_cards( dealer, 1 );

    if( tmp_val == BUSTED ) {
        tell_room( TO, dealer->the_short()+" is busted and settles "
            "the game, paying winnings to all winning players.\n");

        foreach( player in keys(data) ) {
            if( player != dealer )
                pay_out( player, 0 );
        }
        return end_game();
    }

    if( tmp_val == BLACKJACK )
        return settle_game();

    if( tmp_val < 17 ) {
        deal_card(dealer);
        return dealer_game();
    }

    foreach( card in data[dealer]->hand1 ) {
        if( ( i = card_val( card ) ) == 11 ) {
            aces++;
            tmp_val--;
        } else {
            tmp_val -= i;
        }
    }

    if( aces && !tmp_val )
        return settle_game();

    if( aces && tmp_val > 0 ) {
        deal_card( dealer );
        return dealer_game();
    }

    return settle_game();

} /* dealer_game() */

/** @ignore yes */
void switch_player() {
    object player;

    if( main_id ) {
        remove_call_out( main_id );
        main_id = 0;
    }

    if( !sizeof(now_playing) ) {
        tell_room( TO, dealer->the_short()+" clears the table as there is "
            "no-one left playing"+( sizeof(players) ? " this round" : "")+
            ".\n");
        return end_game();
    }

    foreach( player in now_playing ) {
        if( member_array( player, played ) == -1 ) {
            cur_plr = player;
            break;
        }
    }

    if( sizeof(played) == sizeof(now_playing) && !sizeof(data) ) {
        tell_room( TO, "All players are busted and "+
            dealer->the_short()+" clears the table.\n");
        return end_game();
    }

    if( sizeof(played) == sizeof(now_playing) ) {
        cur_plr = dealer;
        tell_room( TO, "It is now "+dealer->poss_short()+" turn.\n");
        return dealer_game();
    }

    tell_room( TO, "It is now "+cur_plr->poss_short()+" turn.\n");
    hands( cur_plr );
    played += ({ cur_plr });

    if( eval_cards( cur_plr, 1 ) == BLACKJACK ) {
        do_say( cur_plr, "You already have a blackjack, so we can skip you.");
        dealer->do_command("beam");
        return switch_player();
    }

    do_announce( cur_plr, 0 );
    main_id = call_out( (: main_game :), 30, cur_plr );

} /* switch_player() */

/** @ignore yes */
int do_statistics() {
    string ret;

    if( !creatorp(TP) ) {
        add_failed_mess("Sorry, only creators can check the game "
            "statistics.\n");
        return 0;
    }

    ret =  "Statistics:\n";
    ret += "Raw income      : "+MSTRING( raw_income )+" ("+raw_income+").\n";
    ret += "Total Winnings  : "+MSTRING( winnings_paid )+" ("+
           winnings_paid+").\n";
    ret += "Profit made     : "+MSTRING( raw_income - winnings_paid )+" ("+
           ( raw_income - winnings_paid )+").\n";
    ret += sprintf("Approximately %i percent of the cash used to play "
           "blackjack has been returned as winnings to the players.\n",
           ( raw_income ? 100 * winnings_paid / raw_income : 0 ) );

    tell_object( TP, ret );
    add_succeeded_mess("");
    return 1;

} /* do_statistics() */

/** @ignore yes */
string extra_look( object ob ) {
    string ret;

    if( ob != TO )
        return "";

    ret = "";

    if( sizeof(players) )
        ret += "$C$"+query_multiple_short( players )+" "+
             ( sizeof(players) == 1 && players[0] != TP ? "is" : "are")+
             " playing cards at the blackjack table";

    if( sizeof(pending) )
        ret += ( ret != "" ? " and " : "$C$")+
            query_multiple_short( pending )+" "+( sizeof(pending) == 1 &&
            pending[0] != TP ? "is" : "are")+" anxious to get into the "
            "game next round";

    return ( ret != "" ? ret+".\n" : "" );

} /* extra_look() */

/** @ignore yes */
mixed stats() {
    return ::stats() + ({
        ({ "raw income", raw_income }),
        ({ "total winnings", winnings_paid }),
        ({ "profit made", raw_income - winnings_paid }),
        ({ "profit percentage", raw_income ?
            100 * winnings_paid / raw_income : 0 }),
        });
} /* stats() */