| blackjack_SOURCES = \ | blackjack_SOURCES = \ | ||||
| src/main.cpp \ | src/main.cpp \ | ||||
| src/blackjack.cpp \ | src/blackjack.cpp \ | ||||
| src/stdinout.cpp | |||||
| src/stdinout.cpp \ | |||||
| src/tty.cpp |
| #ifndef BASE_H | #ifndef BASE_H | ||||
| #define BASE_H | #define BASE_H | ||||
| #include <string> | |||||
| #include <list> | |||||
| // TODO: namespace | |||||
| enum class DealerAction { | enum class DealerAction { | ||||
| None, | None, | ||||
| StartNewHand, | StartNewHand, | ||||
| Payout | Payout | ||||
| }; | }; | ||||
| enum class PlayerAction { | |||||
| enum class PlayerActionRequired { | |||||
| None, | None, | ||||
| Bet, | Bet, | ||||
| Insurance, | Insurance, | ||||
| Play | Play | ||||
| }; | }; | ||||
| enum class Command { | |||||
| enum class PlayerActionTaken { | |||||
| None, | None, | ||||
| // common | // common | ||||
| Quit, | Quit, | ||||
| Table, | Table, | ||||
| // particular | // particular | ||||
| Bet, | Bet, | ||||
| Yes, | |||||
| No, | |||||
| Insure, | |||||
| DontInsure, | |||||
| Stand, | Stand, | ||||
| Double, | Double, | ||||
| Split, | Split, | ||||
| Hit, | 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 { | class Dealer { | ||||
| public: | public: | ||||
| Dealer() {}; | Dealer() {}; | ||||
| Dealer(Dealer &&) = delete; | Dealer(Dealer &&) = delete; | ||||
| Dealer(const 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) { | void setNextAction(DealerAction a) { | ||||
| next_action = a; | next_action = a; | ||||
| } | } | ||||
| void getNextAction(DealerAction a) { | |||||
| next_action = a; | |||||
| } | |||||
| bool getInputNeeded(void) { | bool getInputNeeded(void) { | ||||
| return input_needed; | return input_needed; | ||||
| return (done = d); | return (done = d); | ||||
| } | } | ||||
| private: | |||||
| bool done = false; | bool done = false; | ||||
| bool input_needed = false; | bool input_needed = false; | ||||
| DealerAction next_action = DealerAction::None; | 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 | #endif |
| */ | */ | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <utility> | |||||
| #include "blackjack.h" | #include "blackjack.h" | ||||
| std::cout << "Bye bye! We'll play Blackjack again next time." << std::endl; | 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 zero if it is a common command and we need to ask again | ||||
| // returns positive if what was asked was answered | // returns positive if what was asked was answered | ||||
| // returns negative if what was aked was not asnwered or the command does not apply | // 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); | finished(true); | ||||
| return 1; | 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; | 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; | return 0; |
| Blackjack(); | Blackjack(); | ||||
| ~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 | #endif |
| */ | */ | ||||
| #include <iostream> | #include <iostream> | ||||
| #include <unistd.h> | |||||
| #include "base.h" | #include "base.h" | ||||
| #include "blackjack.h" | #include "blackjack.h" | ||||
| #include "tty.h" | |||||
| #include "stdinout.h" | #include "stdinout.h" | ||||
| int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||
| // TODO: read the args/conf to know what kind of dealer and player we are having | // TODO: read the args/conf to know what kind of dealer and player we are having | ||||
| // TODO: pass args/conf to the constructor | // TODO: pass args/conf to the constructor | ||||
| dealer = new Blackjack(); | 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); | dealer->setNextAction(DealerAction::StartNewHand); | ||||
| Command command{Command::None}; | |||||
| while (!dealer->finished()) { | while (!dealer->finished()) { | ||||
| dealer->setInputNeeded(false); | dealer->setInputNeeded(false); | ||||
| dealer->deal(); | |||||
| dealer->deal(player); | |||||
| if (dealer->getInputNeeded()) { | if (dealer->getInputNeeded()) { | ||||
| do { | do { | ||||
| // TODO: check for too many errors meaning dealer and player do not understand each other | // 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); | |||||
| } | } | ||||
| } | } | ||||
| #include <iostream> | #include <iostream> | ||||
| #ifdef HAVE_LIBREADLINE | |||||
| #include <readline/readline.h> | |||||
| #include <readline/history.h> | |||||
| #endif | |||||
| #include "blackjack.h" | #include "blackjack.h" | ||||
| #include "stdinout.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") { | if (input_buffer == "hit" || input_buffer == "h") { | ||||
| *command = Command::Hit; | *command = Command::Hit; | ||||
| } else { | } else { | ||||
| *command = Command::None; | *command = Command::None; | ||||
| } | } | ||||
| */ | |||||
| return 0; | return 0; | ||||
| } | } |
| class StdInOut : public Player { | class StdInOut : public Player { | ||||
| public: | public: | ||||
| StdInOut() { }; | |||||
| 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 | #endif |
| #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; | |||||
| } |
| #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 |