| @@ -18,4 +18,5 @@ blackjack_LDADD = $(all_libraries) | |||
| blackjack_SOURCES = \ | |||
| src/main.cpp \ | |||
| src/blackjack.cpp \ | |||
| src/stdinout.cpp | |||
| src/stdinout.cpp \ | |||
| src/tty.cpp | |||
| @@ -23,6 +23,11 @@ | |||
| #ifndef BASE_H | |||
| #define BASE_H | |||
| #include <string> | |||
| #include <list> | |||
| // TODO: namespace | |||
| enum class DealerAction { | |||
| None, | |||
| StartNewHand, | |||
| @@ -38,14 +43,14 @@ enum class DealerAction { | |||
| Payout | |||
| }; | |||
| enum class PlayerAction { | |||
| enum class PlayerActionRequired { | |||
| None, | |||
| Bet, | |||
| Insurance, | |||
| Play | |||
| }; | |||
| enum class Command { | |||
| enum class PlayerActionTaken { | |||
| None, | |||
| // common | |||
| Quit, | |||
| @@ -57,14 +62,88 @@ enum class Command { | |||
| Table, | |||
| // particular | |||
| Bet, | |||
| Yes, | |||
| No, | |||
| Insure, | |||
| DontInsure, | |||
| Stand, | |||
| Double, | |||
| Split, | |||
| Hit, | |||
| }; | |||
| #define CARD_ART_LINES 6 | |||
| #define CARD_TYPES 5 | |||
| #define CARD_SIZE 16 | |||
| class Card { | |||
| public: | |||
| int tag; | |||
| int value; | |||
| private: | |||
| std::string token[CARD_TYPES]; | |||
| std::string text; | |||
| std::string art[CARD_ART_LINES]; | |||
| }; | |||
| class Hand { | |||
| public: | |||
| bool insured; | |||
| bool soft; | |||
| bool blackjack; | |||
| bool busted; | |||
| bool holeCardShown; // maybe we need a separate class for dealer and for player? | |||
| int bet; | |||
| int count; | |||
| std::list<Card> cards; | |||
| private: | |||
| }; | |||
| class Player { | |||
| public: | |||
| Player() = default; | |||
| ~Player() = default; | |||
| // delete copy and move constructors | |||
| Player(Player&) = delete; | |||
| Player(const Player&) = delete; | |||
| Player(Player &&) = delete; | |||
| Player(const Player &&) = delete; | |||
| /* | |||
| PlayerAction getNextAction() { | |||
| return nextAction; | |||
| } | |||
| void setNextAction(PlayerAction a) { | |||
| nextAction = a; | |||
| } | |||
| */ | |||
| virtual int play() = 0; | |||
| PlayerActionRequired actionRequired = PlayerActionRequired::None; | |||
| PlayerActionTaken actionTaken = PlayerActionTaken::None; | |||
| bool hasSplit = false; | |||
| bool hasDoubled = false; | |||
| int flatBet = 0; | |||
| int currentBet = 0; | |||
| int n_hands = 0; // this is different from the dealer's due to splitting | |||
| double total_money_waged = 0; | |||
| double current_result = 0; | |||
| double mean = 0; | |||
| double M2 = 0; | |||
| double variance = 0; | |||
| std::list<Hand> hands; | |||
| std::list<Hand>::iterator currentHand; | |||
| }; | |||
| class Dealer { | |||
| public: | |||
| Dealer() {}; | |||
| @@ -75,14 +154,17 @@ class Dealer { | |||
| Dealer(Dealer &&) = delete; | |||
| Dealer(const Dealer &&) = delete; | |||
| virtual void deal() = 0; | |||
| // virtual void ask() = 0; | |||
| virtual int process(Command) = 0; | |||
| virtual void deal(Player *) = 0; | |||
| virtual int process(Player *) = 0; | |||
| void setNextAction(DealerAction a) { | |||
| next_action = a; | |||
| } | |||
| void getNextAction(DealerAction a) { | |||
| next_action = a; | |||
| } | |||
| bool getInputNeeded(void) { | |||
| return input_needed; | |||
| @@ -100,30 +182,14 @@ class Dealer { | |||
| return (done = d); | |||
| } | |||
| private: | |||
| bool done = false; | |||
| bool input_needed = false; | |||
| DealerAction next_action = DealerAction::None; | |||
| PlayerAction player_action = PlayerAction::None; | |||
| Command player_command = Command::None; | |||
| double insurance = 0; | |||
| int bet = 0; | |||
| }; | |||
| class Player { | |||
| public: | |||
| Player() = default; | |||
| ~Player() = default; | |||
| // delete copy and move constructors | |||
| Player(Player&) = delete; | |||
| Player(const Player&) = delete; | |||
| Player(Player &&) = delete; | |||
| Player(const Player &&) = delete; | |||
| // TODO: most of the games will have a single element, but maybe | |||
| // there are games where the dealer has more than one hand | |||
| std::list <Hand> hands; | |||
| virtual int play(Command *, int *) = 0; | |||
| }; | |||
| #endif | |||
| @@ -21,6 +21,7 @@ | |||
| */ | |||
| #include <iostream> | |||
| #include <utility> | |||
| #include "blackjack.h" | |||
| @@ -32,26 +33,230 @@ Blackjack::~Blackjack() { | |||
| std::cout << "Bye bye! We'll play Blackjack again next time." << std::endl; | |||
| } | |||
| void Blackjack::deal() { | |||
| std::cout << "Here are your cards" << std::endl; | |||
| setInputNeeded(true); | |||
| void Blackjack::deal(Player *player) { | |||
| switch(next_action) { | |||
| // ------------------------------------------------------------------------- | |||
| case DealerAction::StartNewHand: | |||
| // check if we are done | |||
| if (n_hands > 0 && n_hand >= n_hands) { | |||
| finished(true); | |||
| return; | |||
| } | |||
| // update the uncertainty (knuth citing welford) | |||
| // The Art of Computer Programming, volume 2: Seminumerical Algorithms, 3rd edn., p. 232 | |||
| // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm | |||
| if (n_hand != 0) { | |||
| double delta = player->current_result - player->mean; | |||
| player->mean += delta / (double)(n_hand); | |||
| player->M2 += delta * (player->current_result - player->mean); | |||
| player->variance = player->M2 / (double)(n_hand); | |||
| } | |||
| infinite_decks_card_number_for_arranged_ones = 0; | |||
| n_hand++; | |||
| // clear dealer's hand, create a new one and add it to the list | |||
| hands.clear(); | |||
| hands.push_back(std::move(Hand())); | |||
| // erase all the player's, create one, add and make it the current one | |||
| player->hands.clear(); | |||
| player->hands.push_back(std::move(Hand())); | |||
| player->currentHand = player->hands.begin(); | |||
| // state that the player did not win anything nor splitted nor doubled down | |||
| player->current_result = 0; | |||
| player->hasSplit = 0; | |||
| player->hasDoubled = 0; | |||
| if (lastPass) { | |||
| // tell people we are shuffling | |||
| // bjcall (blackjack.current_player->write (player, "shuffling")); | |||
| // shuffle the cards | |||
| // shuffle_shoe (); | |||
| // TODO: reset card counting systems | |||
| // burn as many cards as asked | |||
| for (int i = 0; i < number_of_burnt_cards; i++) { | |||
| // dealCard(); | |||
| } | |||
| lastPass = false; | |||
| } | |||
| if (player->flatBet) { | |||
| player->currentHand->bet = player->flatBet; | |||
| setNextAction(DealerAction::DealPlayerFirstCard); | |||
| } else { | |||
| setNextAction(DealerAction::AskForBets); | |||
| } | |||
| // bjcall (blackjack.current_player->write (player, "new_hand")); | |||
| // TODO: think! | |||
| std::cout << "new_hand" << std::endl; | |||
| break; | |||
| // ------------------------------------------------------------------------- | |||
| case DealerAction::AskForBets: | |||
| // step 1. ask for bets | |||
| // TODO: use an output buffer to re-ask in case no number comes back | |||
| std::cout << "bet?" << std::endl; | |||
| // TODO: setter | |||
| player->actionRequired = PlayerActionRequired::Bet; | |||
| setInputNeeded(true); | |||
| break; | |||
| case DealerAction::DealPlayerFirstCard: | |||
| // where's step 2? | |||
| // step 3. deal the first card to each player | |||
| player->n_hands++; // splits are counted as a single hand | |||
| player->total_money_waged += player->currentHand->bet; | |||
| Card card; | |||
| card.tag = 7; | |||
| player->currentHand->cards.push_back(card); | |||
| std::cout << "card_player_first " << player->currentHand->cards.begin()->tag << std::endl; | |||
| // bjcall (write_formatted_card (player, 0, "card_player_first", card)); | |||
| /* | |||
| // step 4. show dealer's upcard | |||
| card = deal_card_to_hand (blackjack.dealer_hand); | |||
| bjcall (write_formatted_card (player, 0, "card_dealer_up", card)); | |||
| if (stdout_opts.isatty) | |||
| { | |||
| print_card_art (card); | |||
| } | |||
| // step 5. deal the second card to each player | |||
| card = deal_card_to_hand (player->current_hand); | |||
| bjcall (write_formatted_card (player, 0, "card_player_second", card)); | |||
| if (stdout_opts.isatty) | |||
| { | |||
| print_hand_art (player->current_hand); | |||
| } | |||
| // TODO: ENHC | |||
| blackjack.next_dealer_action = DEAL_DEALERS_HOLE_CARD; | |||
| break; | |||
| */ | |||
| break; | |||
| /* | |||
| case DealerAction::DealDealerHoleCard: | |||
| break; | |||
| case DealerAction::AskForInsurance: | |||
| break; | |||
| case DealerAction::CheckforBlackjacks: | |||
| break; | |||
| case DealerAction::PayOrTakeInsuranceBets: | |||
| break; | |||
| case DealerAction::AskForPlay: | |||
| std::cout << "Here are your cards" << std::endl; | |||
| setInputNeeded(true); | |||
| break; | |||
| case DealerAction::MoveOnToNextHand: | |||
| break; | |||
| case DealerAction::HitDealerHand: | |||
| break; | |||
| case DealerAction::Payout: | |||
| break; | |||
| case DealerAction::None: | |||
| break; | |||
| */ | |||
| } | |||
| } | |||
| // returns zero if it is a common command and we need to ask again | |||
| // returns positive if what was asked was answered | |||
| // returns negative if what was aked was not asnwered or the command does not apply | |||
| int Blackjack::process(Command command) { | |||
| int Blackjack::process(Player *player) { | |||
| switch (command) { | |||
| case Command::Hit: | |||
| std::cout << "ok, you hit" << std::endl; | |||
| switch (player->actionTaken) { | |||
| // TODO: maybe we have to call a basic method with common commands? | |||
| // we first check common commands | |||
| ///ig+quit+name quit | |||
| ///ig+quit+desc Finish the game | |||
| ///ig+quit+detail Upon receiving this command, the game is finished | |||
| ///ig+quit+detail immediately without even finishing the hand. | |||
| ///ig+quit+detail All IPC resources are unlocked, removed and/or destroyed. | |||
| ///ig+quit+detail The YAML report is written before exiting. | |||
| case PlayerActionTaken::Quit: | |||
| finished(true); | |||
| return 1; | |||
| break; | |||
| case Command::None: | |||
| std::cout << "I don't undertand you" << std::endl; | |||
| break; | |||
| ///ig+help+name help | |||
| ///ig+help+desc Ask for help | |||
| ///ig+help+detail A succinct help message is written on the standard output. | |||
| ///ig+help+detail This command makes sense only when issued by a human player. | |||
| // TODO | |||
| case PlayerActionTaken::Help: | |||
| std::cout << "help yourself" << std::endl; | |||
| return 0; | |||
| break; | |||
| break; | |||
| // TODO: | |||
| case PlayerActionTaken::Count: | |||
| break; | |||
| case PlayerActionTaken::UpcardValue: | |||
| break; | |||
| case PlayerActionTaken::Bankroll: | |||
| break; | |||
| case PlayerActionTaken::Hands: | |||
| break; | |||
| case PlayerActionTaken::Table: | |||
| break; | |||
| case PlayerActionTaken::None: | |||
| return 0; | |||
| break; | |||
| // if we made it this far, the command is particular | |||
| case PlayerActionTaken::Bet: | |||
| // TODO: bet = 0 -> wonging | |||
| if (player->currentBet == 0) { | |||
| std::cout << "bet_zero" << std::endl; | |||
| return 0; | |||
| } else if (player->currentBet < 0) { | |||
| std::cout << "bet_negative" << std::endl; | |||
| return 0; | |||
| } else if (max_bet != 0 && player->currentBet > max_bet) { | |||
| std::cout << "bet_maximum" << max_bet << std::endl; | |||
| return 0; | |||
| } else { | |||
| // ok, valid bet | |||
| player->currentHand->bet = player->currentBet; | |||
| setNextAction(DealerAction::DealPlayerFirstCard); | |||
| return 1; | |||
| } | |||
| break; | |||
| case PlayerActionTaken::Insure: | |||
| player->currentHand->insured = true; | |||
| return 1; | |||
| break; | |||
| case PlayerActionTaken::DontInsure: | |||
| player->currentHand->insured = false; | |||
| return 1; | |||
| break; | |||
| case PlayerActionTaken::Hit: | |||
| std::cout << "ok, you hit" << std::endl; | |||
| finished(true); | |||
| return 1; | |||
| break; | |||
| } | |||
| return 0; | |||
| @@ -28,7 +28,20 @@ class Blackjack : public Dealer { | |||
| Blackjack(); | |||
| ~Blackjack(); | |||
| void deal() override; | |||
| int process(Command) override; | |||
| void deal(Player *) override; | |||
| int process(Player *) override; | |||
| private: | |||
| bool lastPass = false; | |||
| int n_hands = 0; | |||
| int n_hand = 0; | |||
| int max_bet = 0; | |||
| int number_of_burnt_cards = 0; | |||
| int infinite_decks_card_number_for_arranged_ones = 0; | |||
| double insurance = 0; | |||
| }; | |||
| #endif | |||
| @@ -21,9 +21,11 @@ | |||
| */ | |||
| #include <iostream> | |||
| #include <unistd.h> | |||
| #include "base.h" | |||
| #include "blackjack.h" | |||
| #include "tty.h" | |||
| #include "stdinout.h" | |||
| int main(int argc, char **argv) { | |||
| @@ -34,21 +36,23 @@ int main(int argc, char **argv) { | |||
| // TODO: read the args/conf to know what kind of dealer and player we are having | |||
| // TODO: pass args/conf to the constructor | |||
| dealer = new Blackjack(); | |||
| player = new StdInOut(); | |||
| std::cout << "Let's play" << std::endl; | |||
| if (isatty(1)) { | |||
| player = new Tty(); | |||
| } else { | |||
| player = new StdInOut(); | |||
| } | |||
| // TODO: player strategy from file | |||
| dealer->setNextAction(DealerAction::StartNewHand); | |||
| Command command{Command::None}; | |||
| while (!dealer->finished()) { | |||
| dealer->setInputNeeded(false); | |||
| dealer->deal(); | |||
| dealer->deal(player); | |||
| if (dealer->getInputNeeded()) { | |||
| do { | |||
| // TODO: check for too many errors meaning dealer and player do not understand each other | |||
| player->play(&command, nullptr); | |||
| } while (dealer->process(command) <= 0); | |||
| player->play(); | |||
| } while (dealer->process(player) <= 0); | |||
| } | |||
| } | |||
| @@ -1,20 +1,29 @@ | |||
| #include <iostream> | |||
| #ifdef HAVE_LIBREADLINE | |||
| #include <readline/readline.h> | |||
| #include <readline/history.h> | |||
| #endif | |||
| #include "blackjack.h" | |||
| #include "stdinout.h" | |||
| int StdInOut::play(Command *command, int *param) { | |||
| std::cout << "what do you want to do" << std::endl; | |||
| std::string input_buffer; | |||
| std::cin >> input_buffer; | |||
| StdInOut::StdInOut(void) { | |||
| } | |||
| int StdInOut::play() { | |||
| std::cin >> input_buffer; | |||
| // TODO: check EOF | |||
| /* | |||
| if (input_buffer == "hit" || input_buffer == "h") { | |||
| *command = Command::Hit; | |||
| } else { | |||
| *command = Command::None; | |||
| } | |||
| */ | |||
| return 0; | |||
| } | |||
| @@ -4,9 +4,24 @@ | |||
| class StdInOut : public Player { | |||
| public: | |||
| StdInOut() { }; | |||
| StdInOut(); | |||
| ~StdInOut() { }; | |||
| int play(Command *, int *) override; | |||
| int play(void) override; | |||
| private: | |||
| std::string input_buffer; | |||
| std::string black = "\x1B[0m"; | |||
| std::string red = "\x1B[31m"; | |||
| std::string green = "\x1B[32m"; | |||
| std::string yellow = "\x1B[33m"; | |||
| std::string blue = "\x1B[34m"; | |||
| std::string magenta = "\x1B[35m"; | |||
| std::string cyan = "\x1B[36m"; | |||
| std::string white = "\x1B[37m"; | |||
| std::string reset = "\033[0m"; | |||
| }; | |||
| #endif | |||
| @@ -0,0 +1,75 @@ | |||
| #include <iostream> | |||
| #include <cstring> | |||
| #ifdef HAVE_LIBREADLINE | |||
| #include <readline/readline.h> | |||
| #include <readline/history.h> | |||
| #endif | |||
| #include "blackjack.h" | |||
| #include "tty.h" | |||
| Tty::Tty(void) { | |||
| #ifdef HAVE_LIBREADLINE | |||
| prompt = cyan + " > " + reset; | |||
| #endif | |||
| } | |||
| int Tty::play() { | |||
| #ifdef HAVE_LIBREADLINE | |||
| if ((input_buffer = readline(prompt.c_str())) != nullptr) { | |||
| add_history(input_buffer); | |||
| } | |||
| // TODO: check EOF | |||
| // TODO: esto puede ir en algo comun para tty y stdout | |||
| switch (actionRequired) { | |||
| case PlayerActionRequired::Bet: | |||
| // TODO: both as parameters or both as class members | |||
| currentBet = atoi(input_buffer); | |||
| actionTaken = PlayerActionTaken::Bet; | |||
| break; | |||
| case PlayerActionRequired::Insurance: | |||
| if (strcmp(input_buffer, "y") == 0 || strcmp(input_buffer, "yes") == 0) { | |||
| actionTaken = PlayerActionTaken::Insure; | |||
| } else if (strcmp(input_buffer, "n") == 0 || strcmp(input_buffer, "no") == 0) { | |||
| actionTaken = PlayerActionTaken::DontInsure; | |||
| } else { | |||
| // TODO: chosse if we allow not(yes) == no | |||
| actionTaken = PlayerActionTaken::None; | |||
| } | |||
| break; | |||
| case PlayerActionRequired::Play: | |||
| if (strcmp(input_buffer, "h") == 0 || strcmp(input_buffer, "hit") == 0) { | |||
| actionTaken = PlayerActionTaken::Hit; | |||
| } else { | |||
| actionTaken = PlayerActionTaken::None; | |||
| } | |||
| break; | |||
| } | |||
| free(input_buffer); | |||
| #else | |||
| std::cout << prompt; | |||
| std::cin >> input_buffer; | |||
| // TODO: check EOF | |||
| // TODO: esto puede ir en algo comun para tty y stdout | |||
| if (input_buffer == "hit" || input_buffer == "h") { | |||
| *command = Command::Hit; | |||
| } else { | |||
| *command = Command::None; | |||
| } | |||
| #endif | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| #ifndef TTY_H | |||
| #define TTY_H | |||
| #include "blackjack.h" | |||
| class Tty : public Player { | |||
| public: | |||
| Tty(); | |||
| ~Tty() { }; | |||
| int play() override; | |||
| private: | |||
| #ifdef HAVE_LIBREADLINE | |||
| char *input_buffer; | |||
| #else | |||
| std::string input_buffer; | |||
| #endif | |||
| std::string prompt; | |||
| std::string black = "\x1B[0m"; | |||
| std::string red = "\x1B[31m"; | |||
| std::string green = "\x1B[32m"; | |||
| std::string yellow = "\x1B[33m"; | |||
| std::string blue = "\x1B[34m"; | |||
| std::string magenta = "\x1B[35m"; | |||
| std::string cyan = "\x1B[36m"; | |||
| std::string white = "\x1B[37m"; | |||
| std::string reset = "\033[0m"; | |||
| }; | |||
| #endif | |||