diff --git a/include/game_state.hpp b/include/game_state.hpp index d755c9c..d3aa527 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -194,7 +194,7 @@ namespace Hanabi _actions_log.emplace(ActionType::play, played_card, index, _num_clues == 8, strike); - if (is_playable(played_card)) + if (!strike) { --_stacks[played_card.suit]; _score++; @@ -951,31 +951,81 @@ namespace Hanabi // Check for discards now if (_pace > 0 and _num_clues < max_num_clues) { - for (std::uint8_t index = 0; index < hand_size; index++) + // This will hold the index of trash to discard + std::uint8_t const invalid_index = std::numeric_limits::max(); + std::uint8_t discard_index = invalid_index; + + for (hand_index_t index = 0; index < hand_size; index++) { if (is_trash(hand[index])) { - probability_t sum_of_probabilities = 0; - - do_for_each_potential_draw(index, false, [this, &sum_of_probabilities](const unsigned long multiplicity) { - sum_of_probabilities += evaluate_state() * multiplicity; - }); - - const unsigned long total_weight = std::max(static_cast(_weighted_draw_pile_size), 1ul); - const probability_t probability_discard = sum_of_probabilities / total_weight; - best_probability = std::max(best_probability, probability_discard); - - best_probability = std::max(best_probability, probability_discard); - if (best_probability == 1) - { - update_tablebase(id_of_state, best_probability); - return best_probability; - }; + discard_index = index; // All discards are equivalent, do not continue searching for different trash break; } } + + // If no trivial trash found, check for duplicates next + if (discard_index == invalid_index) { + for (std::uint8_t index = 0; index < hand_size; index++) { + Card const card = _hands[_turn][index]; + auto it = std::find_if(_hands[_turn].begin() + index + 1, _hands[_turn].end(), [&card, this](Card const & card_in_hand) { + return card_in_hand == card; + }); + if (it != _hands[_turn].end()) { + // found a duplicate to discard + discard_index = index; + // Since we are discarding essentially trash, we do not have to consider further actions + break; + } + } + } + + // Discard if we found trash now + if (discard_index != invalid_index) { + probability_t sum_of_probabilities = 0; + + do_for_each_potential_draw(discard_index, false, [this, &sum_of_probabilities](const unsigned long multiplicity) { + sum_of_probabilities += evaluate_state() * multiplicity; + }); + + const unsigned long total_weight = std::max(static_cast(_weighted_draw_pile_size), 1ul); + const probability_t probability_discard = sum_of_probabilities / total_weight; + best_probability = std::max(best_probability, probability_discard); + + best_probability = std::max(best_probability, probability_discard); + if (best_probability == 1) + { + update_tablebase(id_of_state, best_probability); + return best_probability; + }; + } else { + // If we reach this state, then there are no dupes in hand, so we need to check if we want to + // sacrifice cards in hand + for(hand_index_t index = 0; index < hand_size; ++index) { + if(!is_critical(hand[index])) { + std::cout << "Considered sacrificing card " << hand[index] << "in the following state:\n" << *this << std::endl; + // consider discarding this + probability_t sum_of_probabilities = 0; + + do_for_each_potential_draw(index, false, [this, &sum_of_probabilities](const unsigned long multiplicity) { + sum_of_probabilities += evaluate_state() * multiplicity; + }); + + const unsigned long total_weight = std::max(static_cast(_weighted_draw_pile_size), 1ul); + const probability_t probability_discard = sum_of_probabilities / total_weight; + best_probability = std::max(best_probability, probability_discard); + + best_probability = std::max(best_probability, probability_discard); + if (best_probability == 1) + { + update_tablebase(id_of_state, best_probability); + return best_probability; + }; + } + } + } } // Last option is to stall