diff --git a/include/caravan/core/training.h b/include/caravan/core/training.h index 87fc6aa..c996c07 100644 --- a/include/caravan/core/training.h +++ b/include/caravan/core/training.h @@ -36,9 +36,6 @@ typedef struct TrainConfig { uint32_t episode{0}; } TrainConfig; -std::uniform_int_distribution dist_first_player(NUM_PLAYER_ABC, NUM_PLAYER_DEF); -std::uniform_int_distribution dist_action(0, SIZE_ACTION_SPACE - 1); -std::uniform_real_distribution dist_explore(0, 1); /* * FUNCTIONS diff --git a/include/caravan/model/caravan.h b/include/caravan/model/caravan.h index c4a07f3..b0909df 100644 --- a/include/caravan/model/caravan.h +++ b/include/caravan/model/caravan.h @@ -11,6 +11,11 @@ #include "caravan/core/common.h" +/** + * A caravan that contains all of the information for a given track of numeral + * cards and any face cards attached to them, including: the total caravan bid, + * its direction, and its suit. + */ class Caravan { protected: @@ -18,41 +23,120 @@ class Caravan { Track track; uint8_t i_track; + /** + * @param rank A numeral rank. + * @return An integer equivalent of the numeral, range: 1-10. + * + * @throws CaravanFatalException If a non-numeral rank is provided. + */ static uint8_t numeral_rank_to_uint8_t(Rank rank); + /** + * @param index The index of the numeral card to remove from the caravan. + */ void remove_numeral_card(uint8_t index); public: /** - * A caravan that contains all of the information for a given track of numeral - * cards and any face cards attached to them, including: the total caravan bid, - * its direction, and its suit. - * - * @param cvname The caravan name. + * @param cvname Caravan name. */ explicit Caravan(CaravanName cvname) : name(cvname), track({}), i_track(0) {}; + /** + * @return Caravan bid. + */ uint16_t get_bid(); - Slot get_slot(uint8_t pos); - + /** + * @return Current caravan direction. + */ Direction get_direction(); + /** + * @return Caravan name. + */ CaravanName get_name(); + /** + * @return Current number of numeral cards in caravan. + */ uint8_t get_size(); + /** + * @param pos Caravan position. + * @return Slot at position. + * @throws CaravanGameException Chosen card position is out of range. + */ + Slot get_slot(uint8_t pos); + + /** + * @return Current caravan suit. + */ Suit get_suit(); + /** + * Remove all cards from the caravan. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + * @throws CaravanGameException Caravan track is empty. + */ bool clear(bool check_only = false); - bool put_numeral_card(Card card, bool check_only = false); - + /** + * @param card Face card to put into caravan. + * @param pos Position of numeral card on which to put the face card. + * @param *target Stores a copy of the numeral card on which the face + * card was placed into this pointer. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + * + * @throws CaravanGameException Caravan position not entered. + * @throws CaravanGameException No numeral card at chosen position. + * @throws CaravanGameException Chosen card is not a face card. + * @throws CaravanGameException Numeral card at maximum face card capacity. + */ bool put_face_card(Card card, uint8_t pos, Card *target = nullptr, bool check_only = false); + /** + * @param card Numeral card to put into caravan. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + * @throws CaravanGameException Card is not a numeral. + * @throws CaravanGameException Caravan is at maximum numeral card capacity. + * @throws CaravanGameException Numeral card has same rank as most recent + * card in caravan. + * @throws CaravanGameException Numeral does not follow caravan direction. + */ + bool put_numeral_card(Card card, bool check_only = false); + + /** + * Remove all numeral cards of a given rank. + * + * @param rank The rank to remove. + * @param pos_exclude The numeral card at the position will be excluded from + * removal. If 0, no card is excluded. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + * @throws CaravanFatalException Exclude position is out of range. + */ bool remove_rank(Rank rank, uint8_t pos_exclude, bool check_only = false); + /** + * Remove all numeral cards of a given suit. + * + * @param suit The suit to remove. + * @param pos_exclude The numeral card at the position will be excluded from + * removal. If 0, no card is excluded. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + * @throws CaravanFatalException Exclude position is out of range. + */ bool remove_suit(Suit suit, uint8_t pos_exclude, bool check_only = false); }; diff --git a/include/caravan/model/deck.h b/include/caravan/model/deck.h index ed56374..1bf7414 100644 --- a/include/caravan/model/deck.h +++ b/include/caravan/model/deck.h @@ -9,13 +9,42 @@ class DeckBuilder { protected: + /** + * @param shuffle If true, the deck is shuffled. If false, the deck is in + * numeral order. + * @return A traditional deck: standard 52 cards + 2 JOKERs. + */ static Deck build_traditional_deck(bool shuffle); + /** + * @param d The deck to shuffle. + * @return A deck with shuffled cards. + */ static Deck shuffle_deck(Deck d); public: DeckBuilder() = delete; + /** + * @param num_cards The number of cards to have in the caravan deck, + * must be between 30 and 162 cards (inclusive). + * @param num_sample_decks The number of standard card decks + * (52 cards + 2 Jokers) from which to sample cards for the caravan + * deck, must be between 1 and 3 (inclusive). + * @param balanced_sample If true, standard decks are sampled in a + * round-robin fashion e.g. a random card from deck 1, then deck 2, + * then 3, then 1, 2, 3, and so on. If false, then standard decks are + * sampled randomly. + * + * @return A caravan deck. + * + * @throws CaravanFatalException Requested number of cards outside of + * acceptable range. + * @throws CaravanFatalException Requested number of sample decks outside of + * acceptable range. + * @throws CaravanFatalException Insufficient cards to sample in order to + * build deck. + */ static Deck *build_caravan_deck( uint8_t num_cards, uint8_t num_sample_decks, diff --git a/include/caravan/model/game.h b/include/caravan/model/game.h index 7508e94..7568a95 100644 --- a/include/caravan/model/game.h +++ b/include/caravan/model/game.h @@ -23,13 +23,18 @@ class Game { bool has_sold(CaravanName cvname); - bool option_clear(Player *pptr, GameCommand *command, bool check_only); + bool option_clear(GameCommand *command, bool check_only); - bool option_discard(Player *pptr, GameCommand *command, bool check_only); + bool option_discard(GameCommand *command, bool check_only); - bool option_play(Player *pptr, GameCommand *command, bool check_only); + bool option_play(GameCommand *command, bool check_only); public: + /** + * @param config Game configuration. + * + * @throws CaravanFatalException Invalid player names. + */ explicit Game(GameConfig *gc); ~Game(); @@ -40,19 +45,19 @@ class Game { PlayerCaravanNames get_player_caravan_names(PlayerName pname); - bool is_caravan_winning(CaravanName cvname); - - bool is_caravan_bust(CaravanName cvname); - PlayerName get_player_turn(); Table *get_table(); PlayerName get_winner(); + bool is_caravan_bust(CaravanName cvname); + + bool is_caravan_winning(CaravanName cvname); + void play_option(GameCommand *command); - bool check_option(GameCommand *command); // TODO + bool check_option(GameCommand *command); }; #endif //CARAVAN_MODEL_GAME_H diff --git a/include/caravan/model/table.h b/include/caravan/model/table.h index 388c59b..8953965 100644 --- a/include/caravan/model/table.h +++ b/include/caravan/model/table.h @@ -25,12 +25,39 @@ class Table { ~Table(); + /** + * @param cvname The caravan to get. + * @return Pointer to the caravan. + * @throws CaravanFatalException Invalid caravan name. + */ Caravan *get_caravan(CaravanName cvname); + /** + * Remove all cards from the caravan. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + */ bool clear_caravan(CaravanName cvname, bool check_only = false); + /** + * @param cvname A caravan name. + * @param card A face card. + * @param pos The position of the numeral card on which to place the face card. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + * @throws CaravanGameException QUEEN not played on latest numeral card in caravan. + */ bool play_face_card(CaravanName cvname, Card card, uint8_t pos, bool check_only = false); + /** + * @param cvname A caravan name. + * @param card A numeral card to place in the caravan. + * @param check_only If true, only check whether operation is valid; + * if false, actually perform the operation. + * @return True if operation or check was successful; false otherwise. + */ bool play_numeral_card(CaravanName cvname, Card card, bool check_only = false); }; diff --git a/src/caravan/core/training.cpp b/src/caravan/core/training.cpp index f092c7b..2831088 100644 --- a/src/caravan/core/training.cpp +++ b/src/caravan/core/training.cpp @@ -2,16 +2,9 @@ // The following code can be redistributed and/or // modified under the terms of the GPL-3.0 License. -#include -#include #include -#include -#include -#include #include #include -#include "caravan/core/exceptions.h" -#include "caravan/core/common.h" #include "caravan/model/game.h" #include "caravan/core/training.h" @@ -146,6 +139,9 @@ void train_on_game(Game *game, QTable &q_table, ActionSpace &action_space, Train std::string action; GameCommand command; std::vector invalid; + + std::uniform_int_distribution dist_action(0, SIZE_ACTION_SPACE - 1); + std::uniform_real_distribution dist_explore(0, 1); bool explore = dist_explore(gen) < tc.explore; while (true) { diff --git a/src/caravan/main.cpp b/src/caravan/main.cpp index 0aa1b61..8e9200c 100644 --- a/src/caravan/main.cpp +++ b/src/caravan/main.cpp @@ -32,6 +32,7 @@ const std::string KEY_IMBALANCED = "imbalanced"; const uint8_t FIRST_ABC = 1; const uint8_t FIRST_DEF = 2; +// TODO docstrings in .h for all files int main(int argc, char *argv[]) { User *user_abc; diff --git a/src/caravan/model/caravan.cpp b/src/caravan/model/caravan.cpp index b04bd6d..03ba05d 100644 --- a/src/caravan/model/caravan.cpp +++ b/src/caravan/model/caravan.cpp @@ -7,31 +7,49 @@ #include "caravan/core/exceptions.h" -/** - * Remove all cards from the caravan. - * - * @throws CaravanGameException Caravan track is empty. +/* + * PROTECTED */ -bool Caravan::clear(bool check_only) { - if (i_track == 0) { - if(check_only) { - return false; - } else { - throw CaravanGameException("Cannot clear empty caravan."); - } +uint8_t Caravan::numeral_rank_to_uint8_t(Rank rank) { + switch (rank) { + case ACE: + return 1; + case TWO: + return 2; + case THREE: + return 3; + case FOUR: + return 4; + case FIVE: + return 5; + case SIX: + return 6; + case SEVEN: + return 7; + case EIGHT: + return 8; + case NINE: + return 9; + case TEN: + return 10; + default: + throw CaravanFatalException("Invalid rank."); } +} - if(!check_only) { - i_track = 0; +void Caravan::remove_numeral_card(uint8_t index) { + for (; (index + 1) < i_track; ++index) { + track[index] = track[index + 1]; } - return true; + i_track -= 1; } -/** - * @return Current bid. +/* + * PUBLIC */ + uint16_t Caravan::get_bid() { uint16_t bid; uint8_t value; @@ -55,24 +73,6 @@ uint16_t Caravan::get_bid() { return bid; } -/** - * @param pos Caravan position. - * @return Slot at position. - * - * @throws CaravanGameException Chosen card position is out of range. - */ -Slot Caravan::get_slot(uint8_t pos) { - if (pos < TRACK_NUMERIC_MIN or pos > i_track) { - throw CaravanGameException( - "The chosen card position is out of range."); - } - - return track[pos - 1]; -} - -/** - * @return Current caravan direction. - */ Direction Caravan::get_direction() { Direction dir; int t_latest; @@ -119,23 +119,23 @@ Direction Caravan::get_direction() { return dir; } -/** - * @return Caravan name. - */ CaravanName Caravan::get_name() { return name; } -/** - * @return Current number of numeral cards in caravan. - */ uint8_t Caravan::get_size() { return i_track; } -/** - * @return Current caravan suit. - */ +Slot Caravan::get_slot(uint8_t pos) { + if (pos < TRACK_NUMERIC_MIN or pos > i_track) { + throw CaravanGameException( + "The chosen card position is out of range."); + } + + return track[pos - 1]; +} + Suit Caravan::get_suit() { Suit last; int t; @@ -164,94 +164,29 @@ Suit Caravan::get_suit() { return last; } -/** - * @param card Numeral card to put into caravan. - * - * @throws CaravanGameException Card is not a numeral. - * @throws CaravanGameException Caravan is at maximum numeral card capacity. - * @throws CaravanGameException Numeral card has same rank as most recent card in caravan. - * @throws CaravanGameException Numeral card does not follow direction of caravan. - */ -bool Caravan::put_numeral_card(Card card, bool check_only) { - Direction dir; - Suit suit; - bool ascends; - bool not_same_suit; - bool not_same_dir; - - if (!is_numeral_card(card)) { - if(check_only) { - return false; - } else { - throw CaravanGameException("The card must be a numeral card."); - } - } - - if (i_track == TRACK_NUMERIC_MAX) { +bool Caravan::clear(bool check_only) { + if (i_track == 0) { if (check_only) { return false; - } else { - throw CaravanGameException( - "The caravan is at its maximum numeral card capacity."); - } - } - if (i_track > 0) { - if (card.rank == track[i_track - 1].card.rank) { - if (check_only) { - return false; - } else { - throw CaravanGameException( - "A numeral card must not have same rank as " - "the most recent card in the caravan."); - } - } - - if (i_track > 1) { - dir = get_direction(); - suit = get_suit(); - ascends = card.rank > track[i_track - 1].card.rank; - - not_same_suit = card.suit != suit; - not_same_dir = (dir == ASCENDING and !ascends) or - (dir == DESCENDING and ascends); - - if (not_same_suit and not_same_dir) { - if (check_only) { - return false; - } else { - throw CaravanGameException( - "The numeral card must follow the caravan's " - "direction or match the caravan's suit."); - } - } + } else { + throw CaravanGameException("Cannot clear empty caravan."); } } - if(!check_only) { - track[i_track] = {card, {}, 0}; - i_track += 1; + if (!check_only) { + i_track = 0; } return true; } -/** - * @param card Face card to put into caravan. - * @param pos Position of numeral card on which to put the face card. - * @return The numeral card on which the face card was placed. - * - * @throws CaravanGameException Caravan position not entered. - * @throws CaravanGameException No numeral card at chosen position. - * @throws CaravanGameException Chosen card is not a face card. - * @throws CaravanGameException Numeral card is at maximum face card capacity. - */ bool Caravan::put_face_card(Card card, uint8_t pos, Card *target, bool check_only) { uint8_t i; Card c_on; if (pos < TRACK_NUMERIC_MIN) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -260,7 +195,7 @@ bool Caravan::put_face_card(Card card, uint8_t pos, Card *target, bool check_onl } if (pos > i_track) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -270,7 +205,7 @@ bool Caravan::put_face_card(Card card, uint8_t pos, Card *target, bool check_onl } if (!is_face_card(card)) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -282,13 +217,13 @@ bool Caravan::put_face_card(Card card, uint8_t pos, Card *target, bool check_onl c_on = track[i].card; if (card.rank == JACK) { - if(!check_only) { + if (!check_only) { remove_numeral_card(i); } } else { if (track[i].i_faces == TRACK_FACE_MAX) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -296,28 +231,83 @@ bool Caravan::put_face_card(Card card, uint8_t pos, Card *target, bool check_onl } } - if(!check_only) { + if (!check_only) { track[i].faces[track[i].i_faces] = card; track[i].i_faces += 1; } } - if(!check_only and target != nullptr) { + if (!check_only and target != nullptr) { *target = c_on; } return true; } -/** - * Remove all numeral cards of a given rank. - * - * @param rank The rank to remove. - * @param pos_exclude The numeral card at the position will be excluded from - * removal. If 0, no card is excluded. - * - * @throws CaravanFatalException Exclude position is out of range. - */ +bool Caravan::put_numeral_card(Card card, bool check_only) { + Direction dir; + Suit suit; + bool ascends; + bool not_same_suit; + bool not_same_dir; + + if (!is_numeral_card(card)) { + if(check_only) { + return false; + } else { + throw CaravanGameException("The card must be a numeral card."); + } + } + + if (i_track == TRACK_NUMERIC_MAX) { + if (check_only) { + return false; + } else { + throw CaravanGameException( + "The caravan is at its maximum numeral card capacity."); + } + } + + if (i_track > 0) { + if (card.rank == track[i_track - 1].card.rank) { + if (check_only) { + return false; + } else { + throw CaravanGameException( + "A numeral card must not have same rank as " + "the most recent card in the caravan."); + } + } + + if (i_track > 1) { + dir = get_direction(); + suit = get_suit(); + ascends = card.rank > track[i_track - 1].card.rank; + + not_same_suit = card.suit != suit; + not_same_dir = (dir == ASCENDING and !ascends) or + (dir == DESCENDING and ascends); + + if (not_same_suit and not_same_dir) { + if (check_only) { + return false; + } else { + throw CaravanGameException( + "The numeral card must follow the caravan's " + "direction or match the caravan's suit."); + } + } + } + } + + if(!check_only) { + track[i_track] = {card, {}, 0}; + i_track += 1; + } + + return true; +} + bool Caravan::remove_rank(Rank rank, uint8_t pos_exclude, bool check_only) { uint8_t i_track_original; @@ -351,15 +341,6 @@ bool Caravan::remove_rank(Rank rank, uint8_t pos_exclude, bool check_only) { return true; } -/** - * Remove all numeral cards of a given suit. - * - * @param suit The suit to remove. - * @param pos_exclude The numeral card at the position will be excluded from - * removal. If 0, no card is excluded. - * - * @throws CaravanFatalException Exclude position is out of range. - */ bool Caravan::remove_suit(Suit suit, uint8_t pos_exclude, bool check_only) { uint8_t i_track_original; @@ -392,51 +373,3 @@ bool Caravan::remove_suit(Suit suit, uint8_t pos_exclude, bool check_only) { return true; } - -/* - * PROTECTED - */ - -/** - * @param rank A numeral rank. - * @return An integer equivalent of the numeral, range: 1-10. - * - * @throws CaravanFatalException If a non-numeral rank is provided. - */ -uint8_t Caravan::numeral_rank_to_uint8_t(Rank rank) { - switch (rank) { - case ACE: - return 1; - case TWO: - return 2; - case THREE: - return 3; - case FOUR: - return 4; - case FIVE: - return 5; - case SIX: - return 6; - case SEVEN: - return 7; - case EIGHT: - return 8; - case NINE: - return 9; - case TEN: - return 10; - default: - throw CaravanFatalException("Invalid rank."); - } -} - -/** - * @param index The index of the numeral card to remove from the caravan. - */ -void Caravan::remove_numeral_card(uint8_t index) { - for (index; (index + 1) < i_track; ++index) { - track[index] = track[index + 1]; - } - - i_track -= 1; -} diff --git a/src/caravan/model/deck.cpp b/src/caravan/model/deck.cpp index c1392de..a067e75 100644 --- a/src/caravan/model/deck.cpp +++ b/src/caravan/model/deck.cpp @@ -10,23 +10,43 @@ #include "caravan/core/common.h" #include "caravan/core/exceptions.h" -/** - * @param num_cards The number of cards to have in the caravan deck, - * must be between 30 and 162 cards (inclusive). - * @param num_sample_decks The number of standard card decks - * (52 cards + 2 Jokers) from which to sample cards for the caravan deck, - * must be between 1 and 3 (inclusive). - * @param balanced_sample If true, standard decks are sampled in a round-robin - * fashion e.g. a random card from deck 1, then deck 2, then 3, then - * 1, 2, 3, and so on. - * If false, then standard decks are sampled randomly. - * - * @return A caravan deck. - * - * @throws CaravanFatalException Requested number of cards outside of acceptable range. - * @throws CaravanFatalException Requested number of sample decks outside of acceptable range. - * @throws CaravanFatalException Insufficient cards to sample in order to build deck. + +/* + * PROTECTED + */ + +Deck DeckBuilder::build_traditional_deck(bool shuffle) { + Deck d; + + for (int i = CLUBS; i <= SPADES; ++i) { + for (int j = ACE; j <= KING; ++j) { + d.push_back({ + static_cast(i), + static_cast(j) + }); + } + } + + d.push_back({NO_SUIT, JOKER}); + d.push_back({NO_SUIT, JOKER}); + + if (shuffle) { + return shuffle_deck(d); + } else { + return d; + } +} + +Deck DeckBuilder::shuffle_deck(Deck d) { + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + std::shuffle(d.begin(), d.end(), std::default_random_engine(seed)); + return d; +} + +/* + * PUBLIC */ + Deck *DeckBuilder::build_caravan_deck( uint8_t num_cards, uint8_t num_sample_decks, @@ -91,8 +111,7 @@ Deck *DeckBuilder::build_caravan_deck( // Sample decks randomly std::random_device rd; std::mt19937 gen(rd()); - std::uniform_int_distribution<> distr( - 0, num_sample_decks - 1); + std::uniform_int_distribution<> distr(0, num_sample_decks - 1); while (d->size() < num_cards) { i_next = distr(gen); @@ -113,43 +132,3 @@ Deck *DeckBuilder::build_caravan_deck( return d; } - -/* - * PROTECTED - */ - -/** - * @param shuffle If true, deck is shuffled. If false, it is in numeral order. - * @return A traditional deck: standard 52 cards + 2 JOKERs. - */ -Deck DeckBuilder::build_traditional_deck(bool shuffle) { - Deck d; - - for (int i = CLUBS; i <= SPADES; ++i) { - for (int j = ACE; j <= KING; ++j) { - d.push_back({ - static_cast(i), - static_cast(j) - }); - } - } - - d.push_back({NO_SUIT, JOKER}); - d.push_back({NO_SUIT, JOKER}); - - if (shuffle) { - return shuffle_deck(d); - } else { - return d; - } -} - -/** - * @param d The deck to shuffle. - * @return A deck with shuffled cards. - */ -Deck DeckBuilder::shuffle_deck(Deck d) { - unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); - std::shuffle(d.begin(), d.end(), std::default_random_engine(seed)); - return d; -} \ No newline at end of file diff --git a/src/caravan/model/game.cpp b/src/caravan/model/game.cpp index 3fa84ad..7fea247 100644 --- a/src/caravan/model/game.cpp +++ b/src/caravan/model/game.cpp @@ -5,200 +5,6 @@ #include "caravan/model/game.h" #include "caravan/core/common.h" -/** - * @param config Game configuration. - * - * @throws CaravanFatalException Invalid player names. - */ -Game::Game(GameConfig *gc) { - if (gc->player_first == NO_PLAYER) { - throw CaravanFatalException("Invalid player name for first player in game configuration."); - } - - Deck *deck_top = DeckBuilder::build_caravan_deck( - gc->player_abc_cards, - gc->player_abc_samples, - gc->player_abc_balanced); - - Deck *deck_bottom = DeckBuilder::build_caravan_deck( - gc->player_def_cards, - gc->player_def_samples, - gc->player_def_balanced); - - table_ptr = new Table(); - pa_ptr = new Player(PLAYER_ABC, deck_bottom); - pb_ptr = new Player(PLAYER_DEF, deck_top); - - p_turn = gc->player_first == pa_ptr->get_name() ? pa_ptr : pb_ptr; -} - -Game::~Game() { - delete table_ptr; - delete pa_ptr; - delete pb_ptr; -} - -Player *Game::get_player(PlayerName pname) { - if (pa_ptr->get_name() == pname) { - return pa_ptr; - } - - if (pb_ptr->get_name() == pname) { - return pb_ptr; - } - - throw CaravanFatalException("Invalid player name."); -} - -PlayerCaravanNames Game::get_player_caravan_names(PlayerName pname) { - if (pa_ptr->get_name() == pname) { - return PlayerCaravanNames{CARAVAN_A, CARAVAN_B, CARAVAN_C}; - } - - if (pb_ptr->get_name() == pname) { - return PlayerCaravanNames{CARAVAN_D, CARAVAN_E, CARAVAN_F}; - } - - throw CaravanFatalException("Invalid player name."); -} - -PlayerName Game::get_player_turn() { - return p_turn->get_name(); -} - -Table *Game::get_table() { - return table_ptr; -} - -PlayerName Game::get_winner() { - uint8_t won_pa = 0; - uint8_t won_pb = 0; - int8_t comp[3]; - - // Check if all three caravans have been sold... - - comp[0] = compare_bids(CARAVAN_A, CARAVAN_D); - comp[1] = compare_bids(CARAVAN_B, CARAVAN_E); - comp[2] = compare_bids(CARAVAN_C, CARAVAN_F); - - for (int i = 0; i < 3; ++i) { - if (comp[i] < 0) { - won_pa += 1; - } else if (comp[i] > 0) { - won_pb += 1; - } else { - // All three must be sold for there to be a winner - break; - } - } - - // Winner is whoever won at least 2 out of the 3 bids - if(won_pa + won_pb == 3) { - if (won_pa >= 2) { - return pa_ptr->get_name(); - - } else if (won_pb >= 2) { - return pb_ptr->get_name(); - } - } - - // Neither player has outbid the other - - // Check if players have empty hands... - - if (pa_ptr->get_size_hand() > 0 and pb_ptr->get_size_hand() == 0) { - return pa_ptr->get_name(); - - } else if (pa_ptr->get_size_hand() == 0 and pb_ptr->get_size_hand() > 0) { - return pb_ptr->get_name(); - } - - // Neither player has an empty hand - - // Nobody has won yet... - - return NO_PLAYER; -} - -void Game::play_option(GameCommand *command) { - if (get_winner() != NO_PLAYER) { - throw CaravanFatalException( - "The game has already been won."); - } - - switch (command->option) { - case OPTION_PLAY: - option_play(p_turn, command, false); - break; - - case OPTION_DISCARD: - if (p_turn->get_moves_count() < MOVES_START_ROUND) { - throw CaravanGameException( - "A player cannot discard a card during " - "the Start round."); - } - - option_discard(p_turn, command, false); - break; - - case OPTION_CLEAR: - if (p_turn->get_moves_count() < MOVES_START_ROUND) { - throw CaravanGameException( - "A player cannot clear a caravan during " - "the Start round."); - } - - option_clear(p_turn, command, false); - break; - - default: - throw CaravanFatalException("Invalid play option."); - } - - p_turn->increment_moves(); - p_turn->maybe_add_card_to_hand(); - - if (pa_ptr->get_name() == p_turn->get_name()) { - p_turn = pb_ptr; - } else { - p_turn = pa_ptr; - } -} - -bool Game::is_caravan_winning(CaravanName cvname) { - if(cvname == NO_CARAVAN) { - return false; - } else { - return winning_bid(cvname, get_opposite_caravan_name(cvname)) == cvname; - } -} - -bool Game::is_caravan_bust(CaravanName cvname) { - if (cvname == NO_CARAVAN) { - return false; - } else { - return table_ptr->get_caravan(cvname)->get_bid() > CARAVAN_SOLD_MAX; - } -} - -CaravanName Game::get_opposite_caravan_name(CaravanName cvname) { - switch (cvname) { - case CARAVAN_A: - return CARAVAN_D; - case CARAVAN_B: - return CARAVAN_E; - case CARAVAN_C: - return CARAVAN_F; - case CARAVAN_D: - return CARAVAN_A; - case CARAVAN_E: - return CARAVAN_B; - case CARAVAN_F: - return CARAVAN_C; - default: - return NO_CARAVAN; - } -} /* * PROTECTED @@ -250,16 +56,26 @@ bool Game::has_sold(CaravanName cvname) { return bid >= CARAVAN_SOLD_MIN and bid <= CARAVAN_SOLD_MAX; } -bool Game::option_clear(Player *pptr, GameCommand *command, bool check_only) { +bool Game::option_clear(GameCommand *command, bool check_only) { + if (p_turn->get_moves_count() < MOVES_START_ROUND) { + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A player cannot clear a caravan during " + "the Start round."); + } + } + // Intentionally not catching fatal exception if player is not ABC or DEF - PlayerCaravanNames pcns = get_player_caravan_names(pptr->get_name()); + PlayerCaravanNames pcns = get_player_caravan_names(p_turn->get_name()); // Invalid for a player to clear their opponent's caravans if (pcns[0] != command->caravan_name and pcns[1] != command->caravan_name and pcns[2] != command->caravan_name) { - if(check_only) { + if (check_only) { return false; } else { @@ -272,14 +88,24 @@ bool Game::option_clear(Player *pptr, GameCommand *command, bool check_only) { return table_ptr->clear_caravan(command->caravan_name, check_only); } -bool Game::option_discard(Player *pptr, GameCommand *command, bool check_only) { +bool Game::option_discard(GameCommand *command, bool check_only) { Card c_discarded; bool result; - result = pptr->discard_from_hand_at( + if (p_turn->get_moves_count() < MOVES_START_ROUND) { + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A player cannot discard a card during " + "the Start round."); + } + } + + result = p_turn->discard_from_hand_at( command->pos_hand, &c_discarded, check_only); - if(!check_only) { + if (!check_only) { // Log discarded card to command command->hand = c_discarded; } @@ -287,40 +113,40 @@ bool Game::option_discard(Player *pptr, GameCommand *command, bool check_only) { return result; } -bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { // TODO apply check_only +bool Game::option_play(GameCommand *command, bool check_only) { Card c_hand; // Intentionally not catching fatal exception if player hand is empty try { - c_hand = pptr->get_from_hand_at(command->pos_hand); + c_hand = p_turn->get_from_hand_at(command->pos_hand); } catch (CaravanGameException &e) { - if(check_only) { + if (check_only) { return false; } else { throw; } } - bool in_start_stage = pptr->get_moves_count() < MOVES_START_ROUND; + bool in_start_stage = p_turn->get_moves_count() < MOVES_START_ROUND; bool pa_playing_num_onto_pa_caravans; bool pb_playing_num_onto_pb_caravans; if (is_numeral_card(c_hand)) { pa_playing_num_onto_pa_caravans = - pptr->get_name() == pa_ptr->get_name() and + p_turn->get_name() == pa_ptr->get_name() and (command->caravan_name == CARAVAN_A or command->caravan_name == CARAVAN_B or command->caravan_name == CARAVAN_C); pb_playing_num_onto_pb_caravans = - pptr->get_name() == pb_ptr->get_name() and + p_turn->get_name() == pb_ptr->get_name() and (command->caravan_name == CARAVAN_D or command->caravan_name == CARAVAN_E or command->caravan_name == CARAVAN_F); if (!(pa_playing_num_onto_pa_caravans or pb_playing_num_onto_pb_caravans)) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -331,7 +157,7 @@ bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { / if (in_start_stage and table_ptr->get_caravan( command->caravan_name)->get_size() > 0) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -340,7 +166,7 @@ bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { / } } - if(!table_ptr->play_numeral_card( + if (!table_ptr->play_numeral_card( command->caravan_name, c_hand, check_only)) { @@ -349,7 +175,7 @@ bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { / } else { // is a face card if (in_start_stage) { - if(check_only) { + if (check_only) { return false; } else { throw CaravanGameException( @@ -357,14 +183,14 @@ bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { / } } - if(!check_only) { + if (!check_only) { // Log to command command->board = table_ptr->get_caravan( command->caravan_name)->get_slot( - command->pos_caravan).card; + command->pos_caravan).card; } - if(!table_ptr->play_face_card( + if (!table_ptr->play_face_card( command->caravan_name, c_hand, command->pos_caravan, @@ -373,10 +199,214 @@ bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { / } } - if(!check_only) { - pptr->discard_from_hand_at( + if (!check_only) { + p_turn->discard_from_hand_at( command->pos_hand, &command->hand, check_only); } return true; } + +/* + * PUBLIC + */ + +Game::Game(GameConfig *gc) { + if (gc->player_first == NO_PLAYER) { + throw CaravanFatalException( + "Invalid player name for first player in game configuration."); + } + + Deck *deck_top = DeckBuilder::build_caravan_deck( + gc->player_abc_cards, + gc->player_abc_samples, + gc->player_abc_balanced); + + Deck *deck_bottom = DeckBuilder::build_caravan_deck( + gc->player_def_cards, + gc->player_def_samples, + gc->player_def_balanced); + + table_ptr = new Table(); + pa_ptr = new Player(PLAYER_ABC, deck_bottom); + pb_ptr = new Player(PLAYER_DEF, deck_top); + + p_turn = gc->player_first == pa_ptr->get_name() ? pa_ptr : pb_ptr; +} + +Game::~Game() { + delete table_ptr; + delete pa_ptr; + delete pb_ptr; +} + +CaravanName Game::get_opposite_caravan_name(CaravanName cvname) { + switch (cvname) { + case CARAVAN_A: + return CARAVAN_D; + case CARAVAN_B: + return CARAVAN_E; + case CARAVAN_C: + return CARAVAN_F; + case CARAVAN_D: + return CARAVAN_A; + case CARAVAN_E: + return CARAVAN_B; + case CARAVAN_F: + return CARAVAN_C; + default: + return NO_CARAVAN; + } +} + +Player *Game::get_player(PlayerName pname) { + if (pa_ptr->get_name() == pname) { + return pa_ptr; + } + + if (pb_ptr->get_name() == pname) { + return pb_ptr; + } + + throw CaravanFatalException("Invalid player name."); +} + +PlayerCaravanNames Game::get_player_caravan_names(PlayerName pname) { + if (pa_ptr->get_name() == pname) { + return PlayerCaravanNames{CARAVAN_A, CARAVAN_B, CARAVAN_C}; + } + + if (pb_ptr->get_name() == pname) { + return PlayerCaravanNames{CARAVAN_D, CARAVAN_E, CARAVAN_F}; + } + + throw CaravanFatalException("Invalid player name."); +} + +PlayerName Game::get_player_turn() { + return p_turn->get_name(); +} + +Table *Game::get_table() { + return table_ptr; +} + +PlayerName Game::get_winner() { + uint8_t won_pa = 0; + uint8_t won_pb = 0; + int8_t comp[3]; + + // Check if all three caravans have been sold... + + comp[0] = compare_bids(CARAVAN_A, CARAVAN_D); + comp[1] = compare_bids(CARAVAN_B, CARAVAN_E); + comp[2] = compare_bids(CARAVAN_C, CARAVAN_F); + + for (int i = 0; i < 3; ++i) { + if (comp[i] < 0) { + won_pa += 1; + } else if (comp[i] > 0) { + won_pb += 1; + } else { + // All three must be sold for there to be a winner + break; + } + } + + // Winner is whoever won at least 2 out of the 3 bids + if(won_pa + won_pb == 3) { + if (won_pa >= 2) { + return pa_ptr->get_name(); + + } else if (won_pb >= 2) { + return pb_ptr->get_name(); + } + } + + // Neither player has outbid the other + + // Check if players have empty hands... + + if (pa_ptr->get_size_hand() > 0 and pb_ptr->get_size_hand() == 0) { + return pa_ptr->get_name(); + + } else if (pa_ptr->get_size_hand() == 0 and pb_ptr->get_size_hand() > 0) { + return pb_ptr->get_name(); + } + + // Neither player has an empty hand + + // Nobody has won yet... + + return NO_PLAYER; +} + +bool Game::is_caravan_bust(CaravanName cvname) { + if (cvname == NO_CARAVAN) { + return false; + } else { + return table_ptr->get_caravan(cvname)->get_bid() > CARAVAN_SOLD_MAX; + } +} + +bool Game::is_caravan_winning(CaravanName cvname) { + if (cvname == NO_CARAVAN) { + return false; + } else { + return winning_bid(cvname, get_opposite_caravan_name(cvname)) == cvname; + } +} + +void Game::play_option(GameCommand *command) { + if (get_winner() != NO_PLAYER) { + throw CaravanFatalException( + "The game has already been won."); + } + + switch (command->option) { + case OPTION_PLAY: + option_play(command, false); + break; + + case OPTION_DISCARD: + option_discard(command, false); + break; + + case OPTION_CLEAR: + option_clear(command, false); + break; + + default: + throw CaravanFatalException("Invalid play option."); + } + + p_turn->increment_moves(); + p_turn->maybe_add_card_to_hand(); + + if (pa_ptr->get_name() == p_turn->get_name()) { + p_turn = pb_ptr; + } else { + p_turn = pa_ptr; + } +} + +bool Game::check_option(GameCommand *command) { + if (get_winner() != NO_PLAYER) { + throw CaravanFatalException( + "The game has already been won."); + } + + switch (command->option) { + case OPTION_PLAY: + return option_play(command, true); + + case OPTION_DISCARD: + return option_discard(command, true); + + case OPTION_CLEAR: + return option_clear(command, true); + + default: + throw CaravanFatalException("Invalid play option."); + } +} diff --git a/src/caravan/model/table.cpp b/src/caravan/model/table.cpp index d498a52..de90661 100644 --- a/src/caravan/model/table.cpp +++ b/src/caravan/model/table.cpp @@ -5,7 +5,10 @@ #include "caravan/model/table.h" #include "caravan/core/exceptions.h" -// TODO revise docstrings, move them to .h? + +/* + * PUBLIC + */ Table::~Table() { delete a; @@ -16,12 +19,6 @@ Table::~Table() { delete f; } -/** - * @param cvname The caravan to get. - * @return Pointer to the caravan. - * - * @throws CaravanFatalException Invalid caravan name. - */ Caravan *Table::get_caravan(CaravanName cvname) { switch (cvname) { case CARAVAN_A: @@ -41,20 +38,10 @@ Caravan *Table::get_caravan(CaravanName cvname) { } } -/** - * @param cvname The caravan to clear. - */ bool Table::clear_caravan(CaravanName cvname, bool check_only) { return get_caravan(cvname)->clear(check_only); } -/** - * @param cvname A caravan name. - * @param card A face card. - * @param pos The position of the numeral card on which to place the face card. - * - * @throws CaravanGameException QUEEN not played on latest numeral card in caravan. - */ bool Table::play_face_card(CaravanName cvname, Card card, uint8_t pos, bool check_only) { // TODO check only // Intentionally not catching fatal exception if no caravan Caravan *cvn_target = get_caravan(cvname); @@ -113,10 +100,6 @@ bool Table::play_face_card(CaravanName cvname, Card card, uint8_t pos, bool chec return true; } -/** - * @param cvname A caravan name. - * @param card A numeral card to place in the caravan. - */ bool Table::play_numeral_card(CaravanName cvname, Card card, bool check_only) { return get_caravan(cvname)->put_numeral_card(card, check_only); } diff --git a/src/caravan/train.cpp b/src/caravan/train.cpp index 64b5b7d..cbfa2bb 100644 --- a/src/caravan/train.cpp +++ b/src/caravan/train.cpp @@ -2,13 +2,8 @@ // The following code can be redistributed and/or // modified under the terms of the GPL-3.0 License. -#include -#include -#include #include -#include #include "cxxopts.hpp" -#include "caravan/core/common.h" #include "caravan/model/game.h" #include "caravan/core/training.h" @@ -26,6 +21,8 @@ int main(int argc, char *argv[]) { // Random number generator std::random_device rd; std::mt19937 gen(rd()); + std::uniform_int_distribution dist_first_player( + NUM_PLAYER_ABC, NUM_PLAYER_DEF); try { // Fill action space with all possible actions