// Pinkfish
// Started Wed May 30 21:37:15 PDT 2001
inherit "/std/room/furniture/games/card_base";
inherit "/std/room/furniture/games/multiplayer_base";
inherit "/std/room/furniture/commercial";
#include <money.h>
#define BLACKJACK_BET 0
#define BLACKJACK_PLAYING 1
#define BLACKJACK_BUST 2
#define BLACKJACK_STAND 3
class player_data {
class playing_card* hand;
int starting_bet;
int state;
}
#define BOARD_TAG "blackjack"
//
// This gives an estimated return on the payment. For example 50%
// return would mean you get back approximately 50% of your money on
// average
//
private int _cost;
private int _finished;
private int _max_bet;
private int _min_bet;
private int _timeout_call;
private int _timeout_length;
private int _num_decks;
private int _soft_seventeen;
private int _double_on_doubles;
private class playing_card* _dealer_hand;
private class playing_card* _deck;
private class playing_card* _discard;
int query_hand_value(class playing_card* cards);
void create() {
multiplayer_base::create();
commercial::create();
} /* create() */
void setup() {
set_name( "table" );
set_short( "blackjack table" );
add_adjective( ({ "blackjack" } ));
add_alias("blackjack");
set_long( "The green felt of the table looks nice and soft, there is "
"a single deck of cards sitting in the middle of the table. "
"The table looks like it will seat 4 people.\n");
set_allowed_positions(({"sitting", "lying", "kneeling", "meditating"}));
set_allowed_room_verbs((["sitting" : "sits" ]));
set_weight(2000);
set_value(240000);
// Minimum bid $1.
_timeout_length = 5 * 60;
_cost = 400;
_num_decks = 1;
add_player_id_type("green", 0);
add_player_id_type("red", 0);
add_player_id_type("blue", 0);
add_player_id_type("maroon", 0);
set_minimum_needed(1);
add_help_file("blackjack");
set_commercial_size(15);
set_commercial_type("gambling");
set_shop_use_types(({ "blackjack" }));
}
/**
* This method shows the current status of the cards.
* @param id the id to show the status for
* @return the status of the cards
*/
string query_card_status() {
string id_bing;
string ret;
string* not_playing;
class player_data data;
class playing_card card;
string* womble;
string place;
ret = "";
not_playing = ({ });
womble = query_player_ids();
place = environment()->query_property("place");
foreach (id_bing in womble) {
if (is_person_playing(id_bing) ||
is_game_started()) {
ret += capitalize(id_bing) + " (" +
query_player_cap_name(id_bing) + ")";
data = query_player_data(id_bing);
if (data) {
if (data->starting_bet) {
ret += " " +
MONEY_HAND->money_value_string(data->starting_bet, place);
if (sizeof(data->hand)) {
foreach (card in data->hand) {
ret += " " + query_card_string(card);
}
}
if (data->state == BLACKJACK_STAND) {
ret += " (stand)";
} else if (data->state == BLACKJACK_BUST) {
ret += " (bust)";
}
ret += "\n";
} else {
ret += " No bet yet.\n";
}
} else {
ret += "\n";
}
} else {
not_playing += ({ id_bing });
}
}
if (sizeof(not_playing)) {
ret += query_multiple_short(map(not_playing, (: capitalize($1) :))) +
" are not playing.\n";
}
ret += "\n";
if (sizeof(_dealer_hand)) {
ret += "Dealer's cards ";
if (!_finished) {
ret += "XXXX";
}
foreach (card in _dealer_hand[(_finished?0:1)..]) {
ret += " " + query_card_string(card);
}
if (query_hand_value(_dealer_hand) > 21) {
ret += " (bust)";
}
ret += "\n";
}
return ret;
}
/** @ignore yes */
string long(string str, int dark) {
if (dark) {
return ::long() +
"It is too dark to make out the pieces on the board.\n";
}
return ::long() + query_card_status();
} /* long() */
/**
* This method returns the next card from the deck.
* @return the next card from the deck
*/
class playing_card query_next_card() {
class playing_card card;
if (!sizeof(_deck)) {
if (!sizeof(_discard)) {
_deck = make_deck(_num_decks, 0);
_deck = shuffle_deck(_deck);
} else {
_deck = make_deck(_num_decks, 0);
_deck = shuffle_deck(_deck);
//_deck = shuffle_deck(_discard);
}
tell_all_players("The dealer shuffles the deck before the next card "
"is dealt.\n");
_discard = ({});
}
card = _deck[0];
_deck = _deck[1..];
return card;
} /* query_next_card() */
/**
* This deals cards to everyone.
*/
void deal_cards(string id) {
class player_data data;
string hands;
if (!sizeof(_dealer_hand)) {
_dealer_hand = ({ query_next_card(), query_next_card() });
}
// Everyone gets one card...
hands = "";
//foreach (id in query_currently_playing_ids()) {
data = query_player_data(id);
data->hand = ({ query_next_card(), query_next_card() });
hands += query_player_cap_name(id) + " hand is: "+
query_card_string(data->hand[0]) + " " +
query_card_string(data->hand[1]) +
" (total " +
query_hand_value(data->hand) + ")\n";
//}
hands += "Dealer's cards: XXXX " +
query_card_string(_dealer_hand[0]) + "\n"
"You need to stay or hit yourself for more cards.\n";
tell_all_players(hands);
} /* deal_cards() */
/** @ignore yes */
int start_game() {
class player_data data;
string id;
randomise_player_numbers();
if (!::start_game()) {
return 0;
}
foreach (id in query_player_ids()) {
data = new(class player_data);
data->starting_bet = 0;
data->hand = ({ });
data->state = BLACKJACK_BET;
set_player_data(id, data);
}
_dealer_hand = ({ });
_finished = 0;
//redeal_cards();
tell_all_players("Place your starting bets.\n");
return 1;
} /* reset_game() */
/**
* This figures out the value of the cards.
* @param cards the crds to check
* @return the value of the hand
*/
int query_hand_value(class playing_card* cards) {
class playing_card bing;
int value;
int no_aces;
foreach (bing in cards) {
if (bing->number == 1) {
no_aces++;
} else if (bing->number >= 10) {
value += 10;
} else {
value += bing->number;
}
}
if (no_aces > 0) {
if (no_aces > 1) {
value += no_aces - 1;
}
if (value <= 10) {
value += 11;
} else {
value += 1;
}
}
return value;
} /* query_hand_value() */
/**
* This figures out if the value is a 'soft' one of the specified type.
* @param cards the crds to check
* @return 1 if it is a soft result or not
*/
int is_soft_result(class playing_card* cards) {
class playing_card bing;
int value;
int no_aces;
foreach (bing in cards) {
if (bing->number == 1) {
no_aces++;
} else if (bing->number >= 10) {
value += 10;
} else {
value += bing->number;
}
}
if (no_aces > 0) {
if (no_aces > 1) {
value += no_aces - 1;
}
if (value <= 10) {
return 1;
}
}
return 0;
} /* query_hand_value() */
/**
* Checks to see if all the people playing have put in their first
* bets.
*/
void finish_bet(string id) {
//string id;
class player_data data;
data = query_player_data(id);
// Move to the next state!
call_out("deal_cards", 2, id);
} /* check_for_finish_bet() */
void complete_round() {
int value;
string stuff;
object ob;
int new_value;
class playing_card card;
class player_data data;
string id;
string place;
int paid;
string* winners;
string* losers;
remove_call_out(_timeout_call);
_timeout_call = 0;
place = environment()->query_property("place");
value = query_hand_value(_dealer_hand);
while (value < 17 ||
(value == 17 && _soft_seventeen && is_soft_result(_dealer_hand))) {
_dealer_hand += ({ query_next_card() });
value = query_hand_value(_dealer_hand);
}
stuff = "";
foreach (card in _dealer_hand) {
stuff += " " + query_card_string(card);
}
if (value > 21) {
stuff += " Total " + value + " (bust)\n";
value = 0;
} else {
stuff += " Total " + value + "\n";
}
winners = ({ });
losers = ({ });
foreach (id in query_currently_playing_ids()) {
data = query_player_data(id);
if (data->state == BLACKJACK_STAND) {
new_value = query_hand_value(data->hand);
if (new_value == 21 && sizeof(data->hand) == 2) {
winners += ({ id });
// They get a pay out.
ob = query_player_object(id);
if (ob) {
ob->adjust_money(MONEY_HAND->create_money_array((data->starting_bet * 5) / 2,
place), place);
}
//_pay_out += (data->starting_bet * 3) / 2;
//_revenue -= data->starting_bet;
adjust_float(-(data->starting_bet * 3) / 2);
if (ob) {
stuff += ob->query_cap_name();
} else {
stuff += id;
}
stuff += " gets a payout of " +
MONEY_HAND->money_value_string((data->starting_bet * 3) / 2, place) +
" (plus their original money back).\n";
paid = 1;
} else if (new_value > value) {
winners += ({ id });
// They get a pay out.
ob = query_player_object(id);
if (ob) {
ob->adjust_money(MONEY_HAND->create_money_array(data->starting_bet * 2,
place), place);
}
//_pay_out += data->starting_bet;
//_revenue -= data->starting_bet;
adjust_float(-data->starting_bet);
if (ob) {
stuff += ob->the_short();
} else {
stuff += id;
}
stuff += " gets a payout of " +
MONEY_HAND->money_value_string(data->starting_bet, place) +
" (plus their original money back).\n";
paid = 1;
} else {
losers += ({ id });
}
}
}
if (!paid) {
stuff += "No one gets paid anything.\n";
}
// Do the dealer.
tell_all_players("The dealer reveals their cards as " +
stuff);
if (sizeof(winners)) {
tell_room(environment(),
query_multiple_short(winners) + " win" +
(sizeof(winners) > 1?"":"s") + " the blackjack hand.\n");
} else {
tell_room(environment(), "No one wins the blackjack hand.\n");
}
finish_game(0);
_finished = 1;
} /* complete_round() */
void force_finish() {
if (is_game_started()) {
complete_round();
}
} /* force_finish() */
/**
* Places your bet.
*/
int do_bet(string str) {
string place;
string id;
int amount;
class player_data data;
if (!is_game_started()) {
add_failed_mess("The game has not started.\n");
return 0;
}
id = find_player_id_of_person(this_player());
if (!id) {
add_failed_mess("You are not playing.\n");
return 0;
}
place = environment()->query_property("place");
amount = MONEY_HAND->value_from_string(str, place);
if (!amount) {
add_failed_mess("Invalid bet amount.\n");
return 0;
}
if (this_player()->query_value_in(place) < amount) {
add_failed_mess("You do not have that much to bid.\n");
return 0;
}
if (amount < _min_bet) {
add_failed_mess("The minimum bet for $D is " +
MONEY_HAND->money_value_string(_min_bet, place) + ".\n");
return 0;
}
if (amount > _max_bet) {
add_failed_mess("The maximum bet for $D is " +
MONEY_HAND->money_value_string(_max_bet, place) + ".\n");
return 0;
}
data = query_player_data(id);
if (data->starting_bet) {
add_failed_mess("You have already bet on $D.\n");
return 0;
}
this_player()->pay_money(MONEY_HAND->create_money_array(amount, place),
place);
//_revenue += amount;
adjust_float(amount);
// This is for the starting bet.
data->starting_bet = amount;
data->state = BLACKJACK_PLAYING;
finish_bet(id);
add_succeeded_mess("$N $V " +
MONEY_HAND->money_value_string(amount, place) +
" on $D.\n");
return 1;
} /* do_bet() */
/**
* This hits you for another card.
*/
int do_hit() {
int value;
int not_done;
string id;
class player_data data;
class playing_card card;
// Get another card.
if (!is_game_started()) {
add_failed_mess("The game has not started.\n");
return 0;
}
id = find_player_id_of_person(this_player());
if (!id) {
add_failed_mess("You are not playing.\n");
return 0;
}
data = query_player_data(id);
if (data->state != BLACKJACK_PLAYING) {
add_failed_mess("You are out of the game and cannot get any more "
"cards.\n");
return 0;
}
if (!sizeof(data->hand)) {
add_failed_mess("You cannot hit before you have been dealt cards.\n");
return 0;
}
card = query_next_card();
data->hand += ({ card });
value = query_hand_value(data->hand);
if (value > 21) {
add_succeeded_mess("$N $V and get$s " + query_card_string(card) +
" giving a total of " + value +
" and going bust on $D.\n");
data->state = BLACKJACK_BUST;
foreach (id in query_currently_playing_ids()) {
data = query_player_data(id);
if (data->state != BLACKJACK_BUST &&
data->state != BLACKJACK_STAND) {
not_done = 1;
}
}
if (!not_done) {
remove_call_out(_timeout_call);
_timeout_call = call_out("complete_round", 2);
}
} else {
add_succeeded_mess("$N $V and gets " + query_card_string(card) +
" giving a total of " + value + " on $D.\n");
}
return 1;
} /* do_hit() */
int do_stand() {
int value;
string id;
class player_data data;
int not_done;
// Get another card.
if (!is_game_started()) {
add_failed_mess("The game has not started.\n");
return 0;
}
id = find_player_id_of_person(this_player());
if (!id) {
add_failed_mess("You are not playing.\n");
return 0;
}
data = query_player_data(id);
if (data->state != BLACKJACK_PLAYING) {
add_failed_mess("You are not playing and set yourself to stand.\n");
return 0;
}
value = query_hand_value(data->hand);
data->state = BLACKJACK_STAND;
foreach (id in query_currently_playing_ids()) {
data = query_player_data(id);
if (data->state != BLACKJACK_BUST &&
data->state != BLACKJACK_STAND) {
not_done = 1;
}
}
if (!not_done) {
remove_call_out(_timeout_call);
_timeout_call = call_out("complete_round", 2);
}
add_succeeded_mess("$N $V with a total of " + value +
" on $D.\n");
return 1;
} /* do_stand() */
/**
* Starts a nice furry game.
*/
int do_start() {
if (!is_open_for("blackjack", this_player()->query_name())) {
add_failed_mess("The blackjack table is not open.\n");
return 0;
}
if (_timeout_call) {
add_failed_mess("Someone is still playing, you cannot start a "
"new game yet.\n");
return 0;
}
//
// There must eb enough money in the float for everyone to bid the max amo
// amount and win with a blackjack.
//
if (query_float() < (_max_bet * 3 * 4) / 2) {
if (is_allowed(this_player()->query_name())) {
add_failed_mess("The float is too low for the table to open.\n");
return 0;
}
add_failed_mess("The blackjack table is not open.\n");
return 0;
}
if (!is_playing(this_player())) {
add_failed_mess("You must be playing the game to start it.\n");
return 0;
}
if (!start_game()) {
add_failed_mess("You need at least three people to play modern art.\n");
return 0;
}
add_succeeded_mess("$N $V a game on $D.\n");
remove_call_out(_timeout_call);
_timeout_call = call_out("force_finish", _timeout_length);
tell_all_players("Timeout for this game is " + (_timeout_length / 60) +
" minutes.\n");
return 1;
} /* do_start() */
/**
* If it is finished early... Oh dear.
*/
int do_finish() {
string person;
if (!is_game_started()) {
add_failed_mess("The game has not started.\n");
return 0;
}
person = find_player_id_of_person(this_player());
if (!person) {
add_failed_mess("You must actually be playing to finish the game.\n");
return 0;
}
force_finish();
return 1;
} /* do_finish() */
string query_main_status(int hint) {
string place;
string ret;
place = query_money_place();
ret = "$I$0=Blackjack table:\n"
"$I$6= Table is " +
(query_float() >= (_max_bet * 3 * 4) / 2?"open.\n":
"closed! (Float needed: " +
MONEY_HAND->money_value_string((_max_bet * 3 * 4) / 2, place) +
"; current: " +
MONEY_HAND->money_value_string(query_float(), place) + ")\n") +
"$I$6= Timeout length: " + (_timeout_length / 60) + " minutes.\n";
if (hint) {
ret += "$I$6= set timeout <number> on <table>\n";
}
ret += "$I$6= Maximum bet: " +
MONEY_HAND->money_value_string(_max_bet, place) + "\n";
if (hint) {
ret += "$I$6= set maximum bet <amount> on <table>\n";
}
ret += "$I$6= Minimum bet: " +
MONEY_HAND->money_value_string(_min_bet, place) + "\n";
if (hint) {
ret += "$I$6= set minimum bet <amount> on <table>\n";
}
ret += "$I$6= Num Decks : " + _num_decks + "\n";
if (hint) {
ret += "$I$6= set num decks <amount> on <table>\n";
}
ret += "$I$6= Soft 17 : " + (_soft_seventeen?"on":"off") + "\n";
if (hint) {
ret += "$I$6= set hit on soft seventeen {on|off} on <table>\n";
}
/*
ret += "$I$6= Doubles : " + (_double_on_doubles?"on":"off") + "\n";
if (hint) {
ret += "$I$6= set doubles {on|off} on <table>\n";
}
*/
ret += "$I$6= Float needed: " +
MONEY_HAND->money_value_string((_max_bet * 3 * 4) / 2, place) +
" (max players * max bet * 3 / 2)\n$I$6= Revenue: " +
MONEY_HAND->money_value_string(query_revenue(), place) +
"\n\n";
return ret;
} /* query_main_status() */
/**
* This method sets the bet boundaries.
* @param str the amount string
* @param max_bet if it a max or min bet to set
*/
int do_set_bet(string str, int max_bet) {
string place;
int value;
if (!is_allowed(this_player()->query_name())) {
add_failed_mess("You are not allowed to change the paramaters of "
"$D.\n");
return 0;
}
place = query_money_place();
value = MONEY_HAND->value_from_string(str, place);
if (!value) {
add_failed_mess("Unable to parse the string " + str + ".\n");
return 0;
}
if (max_bet) {
_max_bet = value;
add_succeeded_mess("$N set$s the maximum bet to " +
MONEY_HAND->money_value_string(value, place) + " on $D.\n");
} else {
_min_bet = value;
add_succeeded_mess("$N set$s the minimum bet to " +
MONEY_HAND->money_value_string(value, place) + " on $D.\n");
}
return 1;
} /* do_set_bet() */
/**
* This method sets the timeout for the table.
*/
int do_set_timeout(int length) {
if (!is_allowed(this_player()->query_name())) {
add_failed_mess("You are not allowed to change the paramaters of "
"$D.\n");
return 0;
}
if (length <= 0) {
add_failed_mess("The timeout must be greator than 0.\n");
return 0;
}
_timeout_length = length * 60;
add_succeeded_mess("$N set$s the timeout on $D to " + length + " minutes.\n");
return 1;
} /* do_set_timeout() */
/**
* This method sets the soft_seventeen for the table.
*/
int do_set_soft_seventeen(int value) {
if (!is_allowed(this_player()->query_name())) {
add_failed_mess("You are not allowed to change the paramaters of "
"$D.\n");
return 0;
}
_soft_seventeen = value;
add_succeeded_mess("$N set$s the soft seventeen dealer hit on $D " +
(value?"on":"off") + ".\n");
return 1;
} /* do_set_soft_seventeen() */
/**
* This method sets the soft_seventeen for the table.
*/
int do_set_double_on_doubles(int value) {
if (!is_allowed(this_player()->query_name())) {
add_failed_mess("You are not allowed to change the paramaters of "
"$D.\n");
return 0;
}
_double_on_doubles = value;
add_succeeded_mess("$N set$s the double on doubles on $D " +
(value?"on":"off") + ".\n");
return 1;
} /* do_set_double_on_doubles() */
/**
* This method sets the timeout for the table.
*/
int do_set_num_decks(int num_decks) {
if (!is_allowed(this_player()->query_name())) {
add_failed_mess("You are not allowed to change the paramaters of "
"$D.\n");
return 0;
}
if (num_decks <= 0) {
add_failed_mess("The timeout must be greator than 0.\n");
return 0;
}
if (num_decks >= 10) {
add_failed_mess("The number of decks must be less than 10.\n");
return 0;
}
_num_decks = num_decks;
add_succeeded_mess("$N set$s the number of decks on $D to " + num_decks +
".\n");
return 1;
} /* do_set_num_decks() */
void init() {
multiplayer_base::init();
commercial::init();
add_command("bet", "<string'amount'> on <direct:object>",
(: do_bet($4[0]) :));
add_command("hit", "on <direct:object>",
(: do_hit() :));
add_command("stay", "on <direct:object>",
(: do_stand() :));
/*
add_command("finish", "game on <direct:object>",
(: do_finish() :));
*/
add_command("start", "[new] game on <direct:object>",
(: do_start() :));
if (environment()->is_allowed(this_player()->query_name())) {
add_command("set", "minimum bet <string'amount'> on <direct:object>",
(: do_set_bet($4[0], 0) :));
add_command("set", "maximum bet <string'amount'> on <direct:object>",
(: do_set_bet($4[0], 1) :));
add_command("set", "timeout <number'minutes'> on <direct:object>",
(: do_set_timeout($4[0]) :));
add_command("set", "num decks <number'num decks'> on <direct:object>",
(: do_set_num_decks($4[0]) :));
/*
add_command("set", "double on doubles {on|off} on <direct:object>",
(: do_set_double_on_doubles($4[0] == "on") :));
*/
add_command("set", "hit on soft seventeen {on|off} on <direct:object>",
(: do_set_soft_seventeen($4[0] == "on") :));
}
} /* init() */
/** @ignore yes */
mapping query_dynamic_auto_load() {
mapping map;
map = commercial::query_dynamic_auto_load();
multiplayer_base::query_dynamic_auto_load(map);
//add_auto_load_value(map, BOARD_TAG, "return", _return);
add_auto_load_value(map, BOARD_TAG, "cost", _cost);
//add_auto_load_value(map, BOARD_TAG, "pay out", _pay_out);
//add_auto_load_value(map, BOARD_TAG, "revenue", _revenue);
add_auto_load_value(map, BOARD_TAG, "dealer hand", _dealer_hand);
add_auto_load_value(map, BOARD_TAG, "deck", _deck);
add_auto_load_value(map, BOARD_TAG, "discard", _discard);
add_auto_load_value(map, BOARD_TAG, "finished", _finished);
add_auto_load_value(map, BOARD_TAG, "max bet", _max_bet);
add_auto_load_value(map, BOARD_TAG, "min bet ", _min_bet);
add_auto_load_value(map, BOARD_TAG, "timeout", _timeout_length);
add_auto_load_value(map, BOARD_TAG, "num decks", _num_decks);
add_auto_load_value(map, BOARD_TAG, "soft seventeen", _soft_seventeen);
add_auto_load_value(map, BOARD_TAG, "double on doublet", _double_on_doubles);
return map;
} /* query_dynamic_auto_load() */
/** @ignore yes */
void init_dynamic_arg(mapping map, object player) {
commercial::init_dynamic_arg(map, player);
multiplayer_base::init_dynamic_arg(map, player);
//_return = query_auto_load_value(map, BOARD_TAG, "return");
_cost = query_auto_load_value(map, BOARD_TAG, "cost");
//_pay_out = query_auto_load_value(map, BOARD_TAG, "pay out");
//_revenue = query_auto_load_value(map, BOARD_TAG, "revenue");
_dealer_hand = query_auto_load_value(map, BOARD_TAG, "dealer hand");
_deck = query_auto_load_value(map, BOARD_TAG, "deck");
_discard = query_auto_load_value(map, BOARD_TAG, "discard");
_finished = query_auto_load_value(map, BOARD_TAG, "finished");
_min_bet = query_auto_load_value(map, BOARD_TAG, "min bet");
_max_bet = query_auto_load_value(map, BOARD_TAG, "max bet");
_timeout_length = query_auto_load_value(map, BOARD_TAG, "timeout");
_num_decks = query_auto_load_value(map, BOARD_TAG, "num decks");
_soft_seventeen = query_auto_load_value(map, BOARD_TAG, "soft seventeen");
_double_on_doubles = query_auto_load_value(map, BOARD_TAG, "double on doublet");
if (!_num_decks) {
_num_decks = 1;
}
if (is_game_started()) {
remove_call_out(_timeout_call);
_timeout_call = call_out("force_finish", _timeout_length);
}
} /* init_dynamic_arg() */