From a3f3814309be7d2bc45b7627989760fb3a0495e8 Mon Sep 17 00:00:00 2001 From: r3w0p Date: Mon, 6 Jan 2025 17:41:59 +0000 Subject: [PATCH] too complex for QL QL is becoming Normal with more steps - make normal better - change to easy, medium, hard and make hard what normal currently is but better --- include/caravan/core/training.h | 39 +++-- src/caravan/core/training.cpp | 301 +++++++++++++++++++------------- 2 files changed, 206 insertions(+), 134 deletions(-) diff --git a/include/caravan/core/training.h b/include/caravan/core/training.h index 03fe643..33f8a8f 100644 --- a/include/caravan/core/training.h +++ b/include/caravan/core/training.h @@ -14,28 +14,43 @@ * CONSTANTS */ -const uint16_t SIZE_ACTION = 5; -const uint16_t SIZE_ACTION_SPACE = 920; -const uint16_t SIZE_GAME_STATE = 38; +const uint16_t SIZE_GAME_STATE = 35; const uint8_t NUM_PLAYER_ABC = 1; const uint8_t NUM_PLAYER_DEF = 2; -const uint8_t KEY_ACTION_NAME = 0; -const uint8_t KEY_ACTION_SUIT = 1; -const uint8_t KEY_ACTION_RANK = 2; -const uint8_t KEY_ACTION_CVN_NAME = 3; -const uint8_t KEY_ACTION_CVN_POS = 4; +// ACTIONS -const uint8_t ACTION_NAME_DISCARD = 1; -const uint8_t ACTION_NAME_CLEAR = 2; -const uint8_t ACTION_NAME_PLAY = 3; +const uint16_t SIZE_ACTION_SPACE = 16; +typedef uint8_t Action; + +const uint8_t ACTION_DISCARD_NUMERAL = 10; +const uint8_t ACTION_DISCARD_JACK = 11; +const uint8_t ACTION_DISCARD_QUEEN = 12; +const uint8_t ACTION_DISCARD_KING = 13; +const uint8_t ACTION_DISCARD_JOKER = 14; + +const uint8_t ACTION_CLEAR_BUST = 20; + +const uint8_t ACTION_PLAY_NUMERAL_1 = 30; +const uint8_t ACTION_PLAY_NUMERAL_2 = 31; +const uint8_t ACTION_PLAY_NUMERAL_3 = 32; + +const uint8_t ACTION_PLAY_JACK_SELF = 40; // on lowest bust +const uint8_t ACTION_PLAY_JACK_OPP = 41; // on highest non-bust + +const uint8_t ACTION_PLAY_QUEEN_SELF = 50; // on LOW+DES, HIGH+ASC, light +const uint8_t ACTION_PLAY_QUEEN_OPP = 51; // on LOW+ASC, HIGH+DES, light + +const uint8_t ACTION_PLAY_KING_SELF = 60; // lowest non-bust, on card that would not cause bust +const uint8_t ACTION_PLAY_KING_OPP = 61; // highest non-bust, on card that would preferably cause bust + +const uint8_t ACTION_PLAY_JOKER = 70; // most frequent opp card, play on self where possible /* * TYPES */ -typedef std::array Action; typedef std::array ActionSpace; typedef std::array GameState; typedef std::map> QTable; diff --git a/src/caravan/core/training.cpp b/src/caravan/core/training.cpp index cae2cc8..f6c47bb 100644 --- a/src/caravan/core/training.cpp +++ b/src/caravan/core/training.cpp @@ -48,27 +48,54 @@ void add_hand_to_game_state(GameState *gs, uint16_t *i_gs, Player *player) { uint8_t hand_size = player->get_size_hand(); std::set hand_set; - // Create ordered, unique set of cards from player's hand + bool is_numeral = false; + bool is_jack = false; + bool is_queen = false; + bool is_king = false; + bool is_joker = false; + + // Check for rank types for (uint8_t i_hand = 0; i_hand < hand_size; i_hand++) { - hand_set.insert(card_to_uint8_t(hand[i_hand])); - } + Card card = hand[i_hand]; - uint8_t hand_set_size = hand_set.size(); - std::set::iterator hand_set_iter = hand_set.begin(); + if (is_numeral_card(card)) { + is_numeral = true; - // Add ordered, unique cards to game state, including spaces - for (uint8_t i_set = 0; i_set < HAND_SIZE_MAX_START; i_set++) { - if (i_set < hand_set_size) { - (*gs)[(*i_gs)++] = *hand_set_iter; - ++hand_set_iter; - } else { - (*gs)[(*i_gs)++] = 0; + } else if (card.suit == JACK) { + is_jack = true; + + } else if (card.suit == QUEEN) { + is_queen = true; + + } else if (card.suit == KING) { + is_king = true; + + } else if (card.suit == JOKER) { + is_joker = true; } } + + (*gs)[(*i_gs)++] = is_numeral; + (*gs)[(*i_gs)++] = is_jack; + (*gs)[(*i_gs)++] = is_queen; + (*gs)[(*i_gs)++] = is_king; + (*gs)[(*i_gs)++] = is_joker; } void add_caravan_to_game_state(GameState *gs, uint16_t *i_gs, Game *game, Caravan *caravan) { uint8_t caravan_size = caravan->get_size(); + uint8_t caravan_bid = caravan->get_bid(); + + // Add bid state + if (caravan_bid < CARAVAN_SOLD_MIN) { // light + (*gs)[(*i_gs)++] = 0; + + } else if (caravan_bid > CARAVAN_SOLD_MAX) { // bust + (*gs)[(*i_gs)++] = 2; + + } else { // sold (but not necessarily winning...) + (*gs)[(*i_gs)++] = 1; + } // Add whether caravan is winning (*gs)[(*i_gs)++] = game->is_caravan_winning(caravan->get_name()); @@ -81,15 +108,6 @@ void add_caravan_to_game_state(GameState *gs, uint16_t *i_gs, Game *game, Carava // Add caravan suit (*gs)[(*i_gs)++] = suit_to_uint8_t(caravan->get_suit()); - - // Add rank of highest numeral - if (caravan_size > 0) { - Slot slot = caravan->get_slot(caravan->get_size()); - (*gs)[(*i_gs)++] = rank_to_uint8_t(slot.card.rank); - - } else { - (*gs)[(*i_gs)++] = 0; - } } void get_game_state(GameState *gs, Game *game, PlayerName pname) { @@ -130,135 +148,174 @@ void get_game_state(GameState *gs, Game *game, PlayerName pname) { void populate_action_space(ActionSpace *as) { uint16_t i_as = 0; - // Add DISCARD actions - // 4 x 13 + 1 = 53 - for (uint8_t s = CLUBS; s <= SPADES; s++) { - for (uint8_t r = ACE; r <= KING; r++) { - (*as)[i_as++] = {ACTION_NAME_DISCARD, s, r, 0, 0}; + (*as)[i_as++] = ACTION_DISCARD_NUMERAL; + (*as)[i_as++] = ACTION_DISCARD_JACK; + (*as)[i_as++] = ACTION_DISCARD_QUEEN; + (*as)[i_as++] = ACTION_DISCARD_KING; + (*as)[i_as++] = ACTION_DISCARD_JOKER; + + (*as)[i_as++] = ACTION_CLEAR_BUST; + + (*as)[i_as++] = ACTION_PLAY_NUMERAL_1; + (*as)[i_as++] = ACTION_PLAY_NUMERAL_2; + (*as)[i_as++] = ACTION_PLAY_NUMERAL_3; + + (*as)[i_as++] = ACTION_PLAY_JACK_SELF; + (*as)[i_as++] = ACTION_PLAY_JACK_OPP; + + (*as)[i_as++] = ACTION_PLAY_QUEEN_SELF; + (*as)[i_as++] = ACTION_PLAY_QUEEN_OPP; + + (*as)[i_as++] = ACTION_PLAY_KING_SELF; + (*as)[i_as++] = ACTION_PLAY_KING_OPP; + + (*as)[i_as++] = ACTION_PLAY_JOKER; +} + +bool generate_discard_numeral(std::string *input, Player *player) { + Hand hand = player->get_hand(); + uint8_t hand_size = player->get_size_hand(); + std::string ret = "D"; + + // Discard first numeral in hand + for (uint8_t i = 0; i < hand_size; i++) { + if (is_numeral_card(hand[i])) { + ret += std::to_string(i+1); } } - (*as)[i_as++] = {ACTION_NAME_DISCARD, NO_SUIT, JOKER, 0, 0}; - // Add CLEAR actions - // 3 only (actual caravan determined based on player) - for (uint8_t n = 1; n <= 3; n++) { - (*as)[i_as++] = {ACTION_NAME_CLEAR, 0, 0, n, 0}; + // Could not find a numeral + if (ret.length() == 1) { + return false; } - // Add PLAY for numeral actions - // 4 x 10 x 6 = 240 - for (uint8_t s = CLUBS; s <= SPADES; s++) { - for (uint8_t r = ACE; r <= TEN; r++) { - for (uint8_t n = 1; n <= 6; n++) { - (*as)[i_as++] = {ACTION_NAME_PLAY, s, r, n, 0}; - } + *input = ret; + return true; +} + +bool generate_discard_rank(std::string *input, Player *player, Rank rank) { + Hand hand = player->get_hand(); + uint8_t hand_size = player->get_size_hand(); + std::string ret = "D"; + + // Discard first card with specified rank in hand + for (uint8_t i = 0; i < hand_size; i++) { + if (hand[i].rank == rank) { + ret += std::to_string(i + 1); } } - // Add PLAY for face actions - // (4 x 3 x 6 x 8) + (6 x 8) = 624 - for (uint8_t s = CLUBS; s <= SPADES; s++) { - for (uint8_t r = JACK; r <= KING; r++) { - for (uint8_t n = 1; n <= 6; n++) { - for (uint8_t t = TRACK_NUMERIC_MIN; t <= TRACK_NUMERIC_MAX; t++) { - (*as)[i_as++] = {ACTION_NAME_PLAY, s, r, n, t}; - } - } - } + // Could not find card with specified rank + if (ret.length() == 1) { + return false; } - for (uint8_t n = 1; n <= 6; n++) { - for (uint8_t t = TRACK_NUMERIC_MIN; t <= TRACK_NUMERIC_MAX; t++) { - (*as)[i_as++] = {ACTION_NAME_PLAY, NO_SUIT, JOKER, n, t}; + + *input = ret; + return true; +} + +bool generate_clear_bust(std::string *input, Game *game, Player *player) { + // Clear busted caravan with the highest bid, if any + + PlayerCaravanNames cvn_names = game->get_player_caravan_names( + player->get_name()); + + uint8_t to_clear = 0; + uint16_t bid_clear = 0; + + for (uint8_t i_cvn = 0; i_cvn < cvn_names.size(); i_cvn++) { + Caravan *cvn = game->get_table()->get_caravan(cvn_names[i_cvn]); + uint16_t bid = cvn->get_bid(); + + // Clear caravan that has bust the most, if any + if (bid > CARAVAN_SOLD_MAX and bid > bid_clear) { + to_clear = i_cvn + 1; + bid_clear = bid; } } - // Total: 53 + 3 + 240 + 624 = 720 + // No bust caravans to clear + if (to_clear == 0) { + return false; + } + + *input = "C" + caravan_letter(cvn_names[to_clear - 1]); + return true; } -bool generate_input(std::string *input, Action *action, Game *game) { - Player *player = game->get_player_turn(); - PlayerName pname = player->get_name(); +bool generate_play_numeral(std::string *input, Game *game, Player *player, uint8_t cvn_num) { + PlayerCaravanNames cvn_names = game->get_player_caravan_names( + player->get_name()); + + Caravan *cvn = game->get_table()->get_caravan(cvn_names[cvn_num-1]); + uint16_t cvn_bid = cvn->get_bid(); + Hand hand = player->get_hand(); + uint8_t hand_size = player->get_size_hand(); + + uint8_t index_best = 0; + uint16_t value_best = 0; - if ((*action)[KEY_ACTION_NAME] == ACTION_NAME_DISCARD) { - Card card = { - static_cast((*action)[KEY_ACTION_SUIT]), - static_cast((*action)[KEY_ACTION_RANK]) - }; + // Discard first numeral in hand + for (uint8_t i = 0; i < hand_size; i++) { + Card card = hand[i]; + if (is_numeral_card(card)) { + // TODO skip if wrong direction + // TODO skip if invalid suit + // TODO skip if adding card would bust the caravan + + if (cvn_bid + numeral_rank_value(card) > CARAVAN_SOLD_MAX) { + continue; + } else { - for (uint8_t i = 0; i < player->get_size_hand(); i++) { - if (hand[i].suit == card.suit and hand[i].rank == card.rank) { - *input = "D" + std::to_string(i+1); - return true; } } + } - } else if ((*action)[KEY_ACTION_NAME] == ACTION_NAME_CLEAR) { - switch ((*action)[KEY_ACTION_CVN_NAME]) { - case 1: - *input = pname == PLAYER_ABC ? "CA" : "CD"; - return true; - case 2: - *input = pname == PLAYER_ABC ? "CB" : "CE"; - return true; - case 3: - *input = pname == PLAYER_ABC ? "CC" : "CF"; - return true; - default: - return false; - } + // No (worthwhile) numeral card to play + if (index_best == 0) { + return false; + } - } else if ((*action)[KEY_ACTION_NAME] == ACTION_NAME_PLAY) { - std::string ret = "P"; + // whichever card is closest to the last rank without causing bust - Card card = { - static_cast((*action)[KEY_ACTION_SUIT]), - static_cast((*action)[KEY_ACTION_RANK]) - }; - for (uint8_t i = 0; i < player->get_size_hand(); i++) { - if (hand[i].suit == card.suit and hand[i].rank == card.rank) { - ret += std::to_string(i+1); - break; - } - } - // Unable to find card in hand - if (ret.length() == 1) { - return false; - } - if ((*action)[KEY_ACTION_CVN_NAME] > 0) { - switch ((*action)[KEY_ACTION_CVN_NAME]) { - case 1: - ret += pname == PLAYER_ABC ? "A" : "D"; - break; - case 2: - ret += pname == PLAYER_ABC ? "B" : "E"; - break; - case 3: - ret += pname == PLAYER_ABC ? "C" : "F"; - break; - case 4: - ret += pname == PLAYER_ABC ? "D" : "A"; - break; - case 5: - ret += pname == PLAYER_ABC ? "E" : "B"; - break; - case 6: - ret += pname == PLAYER_ABC ? "F" : "C"; - break; - default: - return false; - } +} - if ((*action)[KEY_ACTION_CVN_POS] > 0) { - ret += std::to_string((*action)[KEY_ACTION_CVN_POS]); - } +bool generate_input(std::string *input, Action action, Game *game) { + Player *player = game->get_player_turn(); + + if (action >= ACTION_DISCARD_NUMERAL and action <= ACTION_DISCARD_JOKER) { + switch (action) { + case ACTION_DISCARD_NUMERAL: + return generate_discard_numeral(input, player); + case ACTION_DISCARD_JACK: + return generate_discard_rank(input, player, JACK); + case ACTION_DISCARD_QUEEN: + return generate_discard_rank(input, player, QUEEN); + case ACTION_DISCARD_KING: + return generate_discard_rank(input, player, KING); + case ACTION_DISCARD_JOKER: + return generate_discard_rank(input, player, JOKER); + default: + return false; } - *input = ret; - return true; + if (action == ACTION_CLEAR_BUST) { + return generate_clear_bust(input, game, player); + + } else if (action >= ACTION_PLAY_NUMERAL_1 and action <= ACTION_PLAY_NUMERAL_3) { + switch (action) { + case ACTION_PLAY_NUMERAL_1: + break; + case ACTION_PLAY_NUMERAL_2: + break; + case ACTION_PLAY_NUMERAL_3: + break; + default: + return false; } return false;