From 4ca66825e31de83dc9575727d0a535596da95d6a Mon Sep 17 00:00:00 2001 From: r3w0p Date: Mon, 4 Nov 2024 22:39:52 +0000 Subject: [PATCH] check_only needs unit tests check_only hasn't broken existing unit tests need to implement check_option next --- include/caravan/model/caravan.h | 12 +- include/caravan/model/game.h | 6 +- include/caravan/model/player.h | 2 +- include/caravan/model/table.h | 6 +- src/caravan/model/caravan.cpp | 171 +++++++++++++++++++--------- src/caravan/model/game.cpp | 112 +++++++++++------- src/caravan/model/player.cpp | 41 ++++--- src/caravan/model/table.cpp | 45 ++++++-- test/caravan/model/test_caravan.cpp | 4 - test/caravan/model/test_game.cpp | 3 + test/caravan/model/test_player.cpp | 2 +- 11 files changed, 267 insertions(+), 137 deletions(-) diff --git a/include/caravan/model/caravan.h b/include/caravan/model/caravan.h index f6e0daf..c4a07f3 100644 --- a/include/caravan/model/caravan.h +++ b/include/caravan/model/caravan.h @@ -33,8 +33,6 @@ class Caravan { explicit Caravan(CaravanName cvname) : name(cvname), track({}), i_track(0) {}; - void clear(); - uint16_t get_bid(); Slot get_slot(uint8_t pos); @@ -47,13 +45,15 @@ class Caravan { Suit get_suit(); - void put_numeral_card(Card card); + bool clear(bool check_only = false); + + bool put_numeral_card(Card card, bool check_only = false); - Card put_face_card(Card card, uint8_t pos); + bool put_face_card(Card card, uint8_t pos, Card *target = nullptr, bool check_only = false); - void remove_rank(Rank rank, uint8_t pos_exclude); + bool remove_rank(Rank rank, uint8_t pos_exclude, bool check_only = false); - void remove_suit(Suit suit, uint8_t pos_exclude); + bool remove_suit(Suit suit, uint8_t pos_exclude, bool check_only = false); }; #endif //CARAVAN_MODEL_CARAVAN_H diff --git a/include/caravan/model/game.h b/include/caravan/model/game.h index de928f7..7508e94 100644 --- a/include/caravan/model/game.h +++ b/include/caravan/model/game.h @@ -23,11 +23,11 @@ class Game { bool has_sold(CaravanName cvname); - bool option_clear(Player *pptr, GameCommand *command, bool check); + bool option_clear(Player *pptr, GameCommand *command, bool check_only); - bool option_discard(Player *pptr, GameCommand *command, bool check); + bool option_discard(Player *pptr, GameCommand *command, bool check_only); - bool option_play(Player *pptr, GameCommand *command, bool check); + bool option_play(Player *pptr, GameCommand *command, bool check_only); public: explicit Game(GameConfig *gc); diff --git a/include/caravan/model/player.h b/include/caravan/model/player.h index 902d811..537deae 100644 --- a/include/caravan/model/player.h +++ b/include/caravan/model/player.h @@ -38,7 +38,7 @@ class Player { void maybe_add_card_to_hand(); - Card discard_from_hand_at(uint8_t pos); + bool discard_from_hand_at(uint8_t pos, Card *discarded = nullptr, bool check_only = false); }; #endif //CARAVAN_MODEL_PLAYER_H diff --git a/include/caravan/model/table.h b/include/caravan/model/table.h index f13d282..388c59b 100644 --- a/include/caravan/model/table.h +++ b/include/caravan/model/table.h @@ -27,11 +27,11 @@ class Table { Caravan *get_caravan(CaravanName cvname); - void clear_caravan(CaravanName cvname); + bool clear_caravan(CaravanName cvname, bool check_only = false); - void play_face_card(CaravanName cvname, Card card, uint8_t pos); + bool play_face_card(CaravanName cvname, Card card, uint8_t pos, bool check_only = false); - void play_numeral_card(CaravanName cvname, Card card); + bool play_numeral_card(CaravanName cvname, Card card, bool check_only = false); }; #endif //CARAVAN_MODEL_TABLE_H diff --git a/src/caravan/model/caravan.cpp b/src/caravan/model/caravan.cpp index ae9ad8f..b04bd6d 100644 --- a/src/caravan/model/caravan.cpp +++ b/src/caravan/model/caravan.cpp @@ -12,12 +12,21 @@ * * @throws CaravanGameException Caravan track is empty. */ -void Caravan::clear() { +bool Caravan::clear(bool check_only) { if (i_track == 0) { - throw CaravanGameException("Cannot clear empty caravan."); + if(check_only) { + return false; + + } else { + throw CaravanGameException("Cannot clear empty caravan."); + } } - i_track = 0; + if(!check_only) { + i_track = 0; + } + + return true; } /** @@ -163,7 +172,7 @@ Suit Caravan::get_suit() { * @throws CaravanGameException Numeral card has same rank as most recent card in caravan. * @throws CaravanGameException Numeral card does not follow direction of caravan. */ -void Caravan::put_numeral_card(Card card) { +bool Caravan::put_numeral_card(Card card, bool check_only) { Direction dir; Suit suit; bool ascends; @@ -171,20 +180,31 @@ void Caravan::put_numeral_card(Card card) { bool not_same_dir; if (!is_numeral_card(card)) { - throw CaravanGameException( - "The card must be a numeral card."); + if(check_only) { + return false; + } else { + throw CaravanGameException("The card must be a numeral card."); + } } if (i_track == TRACK_NUMERIC_MAX) { - throw CaravanGameException( - "The caravan is at its maximum numeral card capacity."); + 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) { - throw CaravanGameException( - "A numeral card must not have same rank as " - "the most recent card in the caravan."); + 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) { @@ -197,15 +217,23 @@ void Caravan::put_numeral_card(Card card) { (dir == DESCENDING and ascends); if (not_same_suit and not_same_dir) { - throw CaravanGameException( - "The numeral card must follow the caravan's " - "direction or match the caravan's suit."); + if (check_only) { + return false; + } else { + throw CaravanGameException( + "The numeral card must follow the caravan's " + "direction or match the caravan's suit."); + } } } } - track[i_track] = {card, {}, 0}; - i_track += 1; + if(!check_only) { + track[i_track] = {card, {}, 0}; + i_track += 1; + } + + return true; } /** @@ -218,42 +246,67 @@ void Caravan::put_numeral_card(Card card) { * @throws CaravanGameException Chosen card is not a face card. * @throws CaravanGameException Numeral card is at maximum face card capacity. */ -Card Caravan::put_face_card(Card card, uint8_t pos) { +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) { - throw CaravanGameException( - "A caravan position has not been entered."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A caravan position has not been entered."); + } } - if (pos > i_track) { - throw CaravanGameException( - "There is not a numeral card at caravan position " + - std::to_string(pos) + "."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "There is not a numeral card at caravan position " + + std::to_string(pos) + "."); + } } if (!is_face_card(card)) { - throw CaravanGameException("The chosen card must be a face card."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "The chosen card must be a face card."); + } } i = pos - 1; c_on = track[i].card; if (card.rank == JACK) { - remove_numeral_card(i); + if(!check_only) { + remove_numeral_card(i); + } } else { if (track[i].i_faces == TRACK_FACE_MAX) { - throw CaravanGameException("The caravan is at its maximum face card capacity."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "The caravan is at its maximum face card capacity."); + } } - track[i].faces[track[i].i_faces] = card; - track[i].i_faces += 1; + if(!check_only) { + track[i].faces[track[i].i_faces] = card; + track[i].i_faces += 1; + } + } + + if(!check_only and target != nullptr) { + *target = c_on; } - return c_on; + return true; } /** @@ -265,29 +318,37 @@ Card Caravan::put_face_card(Card card, uint8_t pos) { * * @throws CaravanFatalException Exclude position is out of range. */ -void Caravan::remove_rank(Rank rank, uint8_t pos_exclude) { +bool Caravan::remove_rank(Rank rank, uint8_t pos_exclude, bool check_only) { uint8_t i_track_original; if (i_track == 0) { - return; + return true; } if (pos_exclude > i_track) { - throw CaravanFatalException( - "The exclude position is out of range."); + if(check_only) { + return false; + } else { + throw CaravanFatalException( + "The exclude position is out of range."); + } } - i_track_original = i_track; + if(!check_only) { + i_track_original = i_track; - for (int t = i_track_original - 1; t >= 0; --t) { - if (pos_exclude > 0 and t == (pos_exclude - 1)) { - continue; - } + for (int t = i_track_original - 1; t >= 0; --t) { + if (pos_exclude > 0 and t == (pos_exclude - 1)) { + continue; + } - if (track[t].card.rank == rank) { - remove_numeral_card(t); + if (track[t].card.rank == rank) { + remove_numeral_card(t); + } } } + + return true; } /** @@ -299,29 +360,37 @@ void Caravan::remove_rank(Rank rank, uint8_t pos_exclude) { * * @throws CaravanFatalException Exclude position is out of range. */ -void Caravan::remove_suit(Suit suit, uint8_t pos_exclude) { +bool Caravan::remove_suit(Suit suit, uint8_t pos_exclude, bool check_only) { uint8_t i_track_original; if (i_track == 0) { - return; + return true; } if (pos_exclude > i_track) { - throw CaravanFatalException( - "The exclude position is out of range."); + if(check_only) { + return false; + } else { + throw CaravanFatalException( + "The exclude position is out of range."); + } } - i_track_original = i_track; + if(!check_only) { + i_track_original = i_track; - for (int t = i_track_original - 1; t >= 0; --t) { - if (pos_exclude > 0 and t == (pos_exclude - 1)) { - continue; - } + for (int t = i_track_original - 1; t >= 0; --t) { + if (pos_exclude > 0 and t == (pos_exclude - 1)) { + continue; + } - if (track[t].card.suit == suit) { - remove_numeral_card(t); + if (track[t].card.suit == suit) { + remove_numeral_card(t); + } } } + + return true; } /* diff --git a/src/caravan/model/game.cpp b/src/caravan/model/game.cpp index 6770615..3fa84ad 100644 --- a/src/caravan/model/game.cpp +++ b/src/caravan/model/game.cpp @@ -250,7 +250,8 @@ 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) { +bool Game::option_clear(Player *pptr, GameCommand *command, bool check_only) { + // Intentionally not catching fatal exception if player is not ABC or DEF PlayerCaravanNames pcns = get_player_caravan_names(pptr->get_name()); // Invalid for a player to clear their opponent's caravans @@ -258,7 +259,7 @@ bool Game::option_clear(Player *pptr, GameCommand *command, bool check) { pcns[1] != command->caravan_name and pcns[2] != command->caravan_name) { - if(check) { + if(check_only) { return false; } else { @@ -267,36 +268,38 @@ bool Game::option_clear(Player *pptr, GameCommand *command, bool check) { } } - // If player's caravan is not empty, then it is valid to clear it - if(check) { - return table_ptr->get_caravan( - command->caravan_name)->get_size() > 0; - } - // Clear the caravan - table_ptr->clear_caravan(command->caravan_name); - return true; + return table_ptr->clear_caravan(command->caravan_name, check_only); } -bool Game::option_discard(Player *pptr, GameCommand *command, bool check) { - Card c_hand; - uint8_t size_hand; +bool Game::option_discard(Player *pptr, GameCommand *command, bool check_only) { + Card c_discarded; + bool result; - if(check) { - size_hand = pptr->get_size_hand(); - return size_hand > 0 and command->pos_hand <= size_hand; - } + result = pptr->discard_from_hand_at( + command->pos_hand, &c_discarded, check_only); - c_hand = pptr->discard_from_hand_at(command->pos_hand); + if(!check_only) { + // Log discarded card to command + command->hand = c_discarded; + } - command->hand = c_hand; // Log to command - return true; + return result; } -bool Game::option_play(Player *pptr, GameCommand *command, bool check) { // TODO check - Card c_hand = pptr->get_from_hand_at(command->pos_hand); +bool Game::option_play(Player *pptr, GameCommand *command, bool check_only) { // TODO apply check_only + Card c_hand; - command->hand = c_hand; // Log to command + // Intentionally not catching fatal exception if player hand is empty + try { + c_hand = pptr->get_from_hand_at(command->pos_hand); + } catch (CaravanGameException &e) { + if(check_only) { + return false; + } else { + throw; + } + } bool in_start_stage = pptr->get_moves_count() < MOVES_START_ROUND; bool pa_playing_num_onto_pa_caravans; @@ -317,34 +320,63 @@ bool Game::option_play(Player *pptr, GameCommand *command, bool check) { // TOD if (!(pa_playing_num_onto_pa_caravans or pb_playing_num_onto_pb_caravans)) { - throw CaravanGameException( - "A numeral card can only be played on " - "a player's own caravan."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A numeral card can only be played on " + "a player's own caravan."); + } } - if (in_start_stage and - table_ptr->get_caravan(command->caravan_name)->get_size() > 0) { - throw CaravanGameException( - "A numeral card must be played on an empty caravan " - "during the Start round."); + if (in_start_stage and table_ptr->get_caravan( + command->caravan_name)->get_size() > 0) { + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A numeral card must be played on an empty caravan " + "during the Start round."); + } } - table_ptr->play_numeral_card(command->caravan_name, c_hand); + if(!table_ptr->play_numeral_card( + command->caravan_name, + c_hand, + check_only)) { + return false; + } } else { // is a face card if (in_start_stage) { - throw CaravanGameException( - "A face card cannot be played during the " - "Start round."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A face card cannot be played during the Start round."); + } + } + + if(!check_only) { + // Log to command + command->board = table_ptr->get_caravan( + command->caravan_name)->get_slot( + command->pos_caravan).card; } - // Log to command - command->board = table_ptr->get_caravan(command->caravan_name)->get_slot(command->pos_caravan).card; - table_ptr->play_face_card( + if(!table_ptr->play_face_card( command->caravan_name, c_hand, - command->pos_caravan); + command->pos_caravan, + check_only)) { + return false; + } + } + + if(!check_only) { + pptr->discard_from_hand_at( + command->pos_hand, &command->hand, check_only); } - pptr->discard_from_hand_at(command->pos_hand); + return true; } diff --git a/src/caravan/model/player.cpp b/src/caravan/model/player.cpp index 634b202..4ecb704 100644 --- a/src/caravan/model/player.cpp +++ b/src/caravan/model/player.cpp @@ -71,29 +71,38 @@ void Player::maybe_add_card_to_hand() { } } -Card Player::discard_from_hand_at(uint8_t pos) { - uint8_t i; - Card c_ret; - +bool Player::discard_from_hand_at(uint8_t pos, Card *discarded, bool check_only) { if (i_hand == 0) { - throw CaravanFatalException( - "Player's hand is empty."); + if(check_only) { + return false; + } else { + throw CaravanFatalException("Player's hand is empty."); + } } if (pos < HAND_POS_MIN or pos > i_hand) { - throw CaravanGameException( - "The chosen hand position is out of range."); + if (check_only) { + return false; + } else { + throw CaravanGameException( + "The chosen hand position is out of range."); + } } - i = pos - 1; - c_ret = hand[i]; + if(!check_only) { + uint8_t i = pos - 1; - // Move the cards above it downwards - for (i; (i + 1) < i_hand; ++i) { - hand[i] = hand[i + 1]; - } + if(discarded != nullptr) { + *discarded = hand[i]; + } + + // Move the cards above it downwards + for (; (i + 1) < i_hand; ++i) { + hand[i] = hand[i + 1]; + } - i_hand -= 1; + i_hand -= 1; + } - return c_ret; + return true; } diff --git a/src/caravan/model/table.cpp b/src/caravan/model/table.cpp index f84fef3..d498a52 100644 --- a/src/caravan/model/table.cpp +++ b/src/caravan/model/table.cpp @@ -5,6 +5,8 @@ #include "caravan/model/table.h" #include "caravan/core/exceptions.h" +// TODO revise docstrings, move them to .h? + Table::~Table() { delete a; delete b; @@ -42,8 +44,8 @@ Caravan *Table::get_caravan(CaravanName cvname) { /** * @param cvname The caravan to clear. */ -void Table::clear_caravan(CaravanName cvname) { - get_caravan(cvname)->clear(); +bool Table::clear_caravan(CaravanName cvname, bool check_only) { + return get_caravan(cvname)->clear(check_only); } /** @@ -53,25 +55,38 @@ void Table::clear_caravan(CaravanName cvname) { * * @throws CaravanGameException QUEEN not played on latest numeral card in caravan. */ -void Table::play_face_card(CaravanName cvname, Card card, uint8_t pos) { +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); if (card.rank == QUEEN and pos != cvn_target->get_size()) { - throw CaravanGameException( - "A QUEEN can only be played on the latest numeral card in a caravan."); + if(check_only) { + return false; + } else { + throw CaravanGameException( + "A QUEEN can only be played on " + "the latest numeral card in a caravan."); + } } // Play Face card on Caravan. // Returns the Numeric card that the Face card was played on. - Card c_target = cvn_target->put_face_card(card, pos); + Card c_target; + if(!cvn_target->put_face_card(card, pos, &c_target, check_only)) { + return false; + } // Process effect of JOKER across all caravans if (card.rank == JOKER) { // Remove from original caravan, excluding the affected card. if (c_target.rank == ACE) { - cvn_target->remove_suit(c_target.suit, pos); + if(!cvn_target->remove_suit(c_target.suit, pos, check_only)) { + return false; + } } else { - cvn_target->remove_rank(c_target.rank, pos); + if(!cvn_target->remove_rank(c_target.rank, pos, check_only)) { + return false; + } } // Remove from other caravans, not excluding any cards. @@ -84,18 +99,24 @@ void Table::play_face_card(CaravanName cvname, Card card, uint8_t pos) { } if (c_target.rank == ACE) { - p_next->remove_suit(c_target.suit, 0); + if(!p_next->remove_suit(c_target.suit, 0, check_only)) { + return false; + } } else { - p_next->remove_rank(c_target.rank, 0); + if(!p_next->remove_rank(c_target.rank, 0, check_only)) { + return false; + } } } } + + return true; } /** * @param cvname A caravan name. * @param card A numeral card to place in the caravan. */ -void Table::play_numeral_card(CaravanName cvname, Card card) { - get_caravan(cvname)->put_numeral_card(card); +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/test/caravan/model/test_caravan.cpp b/test/caravan/model/test_caravan.cpp index fe00290..6548242 100644 --- a/test/caravan/model/test_caravan.cpp +++ b/test/caravan/model/test_caravan.cpp @@ -376,7 +376,6 @@ TEST (TestCaravan, PutFaceCard) { TEST (TestCaravan, PutFaceCard_Error_EmptyCaravan) { auto cvn = Caravan(CARAVAN_D); Card c_face = {HEARTS, KING}; - Slot ts; try { cvn.put_face_card(c_face, 1); @@ -393,7 +392,6 @@ TEST (TestCaravan, PutFaceCard_Error_OutOfRange) { auto cvn = Caravan(CARAVAN_D); Card c_num = {SPADES, ACE}; Card c_face = {HEARTS, KING}; - Slot ts; cvn.put_numeral_card(c_num); @@ -412,7 +410,6 @@ TEST (TestCaravan, PutFaceCard_Error_NotFaceCard) { auto cvn = Caravan(CARAVAN_D); Card c_num_1 = {SPADES, ACE}; Card c_num_2 = {SPADES, TWO}; - Slot ts; cvn.put_numeral_card(c_num_1); @@ -434,7 +431,6 @@ TEST (TestCaravan, PutFaceCard_Error_FullFaceCardCapacity) { Card c_face_2 = {HEARTS, KING}; Card c_face_3 = {HEARTS, KING}; Card c_face_4 = {HEARTS, KING}; - Slot ts; cvn.put_numeral_card(c_num); cvn.put_face_card(c_face_1, 1); diff --git a/test/caravan/model/test_game.cpp b/test/caravan/model/test_game.cpp index edbfa49..c80d653 100644 --- a/test/caravan/model/test_game.cpp +++ b/test/caravan/model/test_game.cpp @@ -5,6 +5,9 @@ #include "gtest/gtest.h" #include "caravan/model/game.h" +// TODO tests for game option check_only +// check_only == True should result in valid play +// check_only == False should result in exception on play attempt TEST (TestGame, GetPlayer_Both) { GameConfig gc = { diff --git a/test/caravan/model/test_player.cpp b/test/caravan/model/test_player.cpp index c9b9351..9ceb84c 100644 --- a/test/caravan/model/test_player.cpp +++ b/test/caravan/model/test_player.cpp @@ -124,7 +124,7 @@ TEST (TestPlayer, RemoveFromHandAt_Position1_StartRound) { ASSERT_EQ(pl.get_size_hand(), 8); c_get = pl.get_hand()[0]; - c_take = pl.discard_from_hand_at(1); + pl.discard_from_hand_at(1, &c_take); pl.increment_moves(); pl.maybe_add_card_to_hand();