deck analyzer: more detailed results
This commit is contained in:
parent
a4b9112ce7
commit
e29a26ed09
3 changed files with 50 additions and 22 deletions
|
@ -112,10 +112,10 @@ def solve_instance(instance: hanab_game.HanabiInstance)-> SolutionData:
|
|||
retval = SolutionData()
|
||||
# first, sanity check on running out of pace
|
||||
result = deck_analyzer.analyze(instance)
|
||||
if len(result) != 0:
|
||||
if len(result.infeasibility_reasons) != 0:
|
||||
logger.verbose("found infeasible deck by preliminary analysis")
|
||||
retval.feasible = False
|
||||
retval.infeasibility_reasons = result
|
||||
retval.infeasibility_reasons = result.infeasibility_reasons
|
||||
return retval
|
||||
for num_remaining_cards in [0, 10, 20]:
|
||||
# logger.info("trying with {} remaining cards".format(num_remaining_cards))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import collections
|
||||
import dataclasses
|
||||
from enum import Enum
|
||||
from typing import List, Any, Optional, Tuple, Set
|
||||
from dataclasses import dataclass
|
||||
|
@ -139,8 +140,30 @@ def analyze_2p_bottom_loss(instance: hanab_game.HanabiInstance) -> List[Infeasib
|
|||
return reasons
|
||||
|
||||
|
||||
def analyze_pace_and_hand_size(instance: hanab_game.HanabiInstance, do_squeeze: bool = True) -> List[InfeasibilityReason]:
|
||||
reasons = []
|
||||
@dataclass
|
||||
class ValueWithIndex:
|
||||
value: int
|
||||
index: int
|
||||
stores_minimum: bool = True
|
||||
|
||||
def update(self, value, index):
|
||||
if (self.stores_minimum and value < self.value) or (not self.stores_minimum and value > self.value):
|
||||
self.value = value
|
||||
self.index = index
|
||||
|
||||
def __repr__(self):
|
||||
return "{} (at {})".format(self.value, self.index)
|
||||
|
||||
@dataclass
|
||||
class AnalysisResult:
|
||||
infeasibility_reasons: List[InfeasibilityReason] = dataclasses.field(default_factory=lambda: [])
|
||||
min_pace: ValueWithIndex = dataclasses.field(default_factory=lambda: ValueWithIndex(100, 0, True))
|
||||
max_stored_crits: ValueWithIndex = dataclasses.field(default_factory=lambda: ValueWithIndex(0, 0, False))
|
||||
max_stored_cards: ValueWithIndex = dataclasses.field(default_factory=lambda: ValueWithIndex(0, 0, False))
|
||||
|
||||
|
||||
def analyze_pace_and_hand_size(instance: hanab_game.HanabiInstance, do_squeeze: bool = True) -> AnalysisResult:
|
||||
reasons = AnalysisResult()
|
||||
# we will sweep through the deck and pretend that
|
||||
# - we keep all non-trash cards in our hands
|
||||
# - we instantly play all playable cards as soon as we have them
|
||||
|
@ -171,6 +194,7 @@ def analyze_pace_and_hand_size(instance: hanab_game.HanabiInstance, do_squeeze:
|
|||
squeeze = False
|
||||
artificial_crits = set()
|
||||
|
||||
|
||||
# Investigate BDRs. This catches special cases of Pace losses in 2p, as well as mark some cards critical because
|
||||
# their second copies cannot be used.
|
||||
filtered_deck = [card for card in instance.deck if card.rank != 5]
|
||||
|
@ -223,7 +247,7 @@ def analyze_pace_and_hand_size(instance: hanab_game.HanabiInstance, do_squeeze:
|
|||
|
||||
# Use a bool flag to only mark this reason once
|
||||
if hand_size_left_for_crits < 0 and not hand_size_found:
|
||||
reasons.append(InfeasibilityReason(InfeasibilityType.HandSize, card_index))
|
||||
reasons.infeasibility_reasons.append(InfeasibilityReason(InfeasibilityType.HandSize, card_index))
|
||||
hand_size_found = True
|
||||
|
||||
# the last - 1 is there because we have to discard 'next', causing a further draw
|
||||
|
@ -233,42 +257,46 @@ def analyze_pace_and_hand_size(instance: hanab_game.HanabiInstance, do_squeeze:
|
|||
if cur_pace < 0 and not pace_found:
|
||||
if squeeze:
|
||||
# We checked single-suit pace losses beforehand (which can only occur in 2p)
|
||||
reasons.append(InfeasibilityReason(InfeasibilityType.PaceAfterSqueeze, card_index))
|
||||
reasons.infeasibility_reasons.append(InfeasibilityReason(InfeasibilityType.PaceAfterSqueeze, card_index))
|
||||
else:
|
||||
reasons.append(InfeasibilityReason(InfeasibilityType.Pace, card_index))
|
||||
reasons.infeasibility_reasons.append(InfeasibilityReason(InfeasibilityType.Pace, card_index))
|
||||
|
||||
pace_found = True
|
||||
|
||||
# if card_index != instance.deck_size - 1:
|
||||
reasons.min_pace.update(cur_pace, card_index)
|
||||
reasons.max_stored_cards.update(len(stored_cards), card_index)
|
||||
reasons.max_stored_crits.update(len(stored_crits), card_index)
|
||||
|
||||
return reasons
|
||||
|
||||
|
||||
def analyze(instance: hanab_game.HanabiInstance) -> List[InfeasibilityReason]:
|
||||
reasons: List[InfeasibilityReason] = []
|
||||
def analyze(instance: hanab_game.HanabiInstance) -> AnalysisResult:
|
||||
# Check for pace and hand size problems:
|
||||
result = analyze_pace_and_hand_size(instance)
|
||||
# In case pace ran out after a squeeze from hand size, we want to run a clean pace analysis again
|
||||
if any(map(lambda r: r.type == InfeasibilityType.PaceAfterSqueeze, result.infeasibility_reasons)):
|
||||
result.infeasibility_reasons += analyze_pace_and_hand_size(instance, False).infeasibility_reasons
|
||||
|
||||
# Top/bottom deck losses in a single suit.
|
||||
top_bottom_deck_loss = check_for_top_bottom_deck_loss(instance)
|
||||
if top_bottom_deck_loss is not None:
|
||||
reasons.append(InfeasibilityReason(InfeasibilityType.BottomTopDeck, top_bottom_deck_loss))
|
||||
result.infeasibility_reasons.append(InfeasibilityReason(InfeasibilityType.BottomTopDeck, top_bottom_deck_loss))
|
||||
|
||||
# Special cases of pace loss, categorization for 2p only
|
||||
reasons += analyze_2p_bottom_loss(instance)
|
||||
result.infeasibility_reasons += analyze_2p_bottom_loss(instance)
|
||||
|
||||
# check for critical non-fives at bottom of the deck
|
||||
bottom_card = instance.deck[-1]
|
||||
if bottom_card.rank != 5 and bottom_card.suitIndex in instance.dark_suits:
|
||||
reasons.append(InfeasibilityReason(
|
||||
result.infeasibility_reasons.append(InfeasibilityReason(
|
||||
InfeasibilityType.CritAtBottom,
|
||||
instance.deck_size - 1
|
||||
))
|
||||
|
||||
# Check for pace and hand size problems:
|
||||
reasons += analyze_pace_and_hand_size(instance)
|
||||
# In case pace ran out after a squeeze from hand size, we want to run a clean pace analysis again
|
||||
if any(map(lambda r: r.type == InfeasibilityType.PaceAfterSqueeze, reasons)):
|
||||
reasons += analyze_pace_and_hand_size(instance, False)
|
||||
|
||||
# clean up reasons to unique
|
||||
return list(set(reasons))
|
||||
result.infeasibility_reasons = list(set(result.infeasibility_reasons))
|
||||
return result
|
||||
|
||||
|
||||
def run_on_database(variant_id):
|
||||
|
@ -281,8 +309,8 @@ def run_on_database(variant_id):
|
|||
with alive_progress.alive_bar(total=len(res), title='Check for infeasibility reasons in var {}'.format(variant_id)) as bar:
|
||||
for (seed, num_players, deck_str) in res:
|
||||
deck = compress.decompress_deck(deck_str)
|
||||
reasons = analyze(hanab_game.HanabiInstance(deck, num_players))
|
||||
for reason in reasons:
|
||||
result = analyze(hanab_game.HanabiInstance(deck, num_players))
|
||||
for reason in result.infeasibility_reasons:
|
||||
database.cur.execute(
|
||||
"INSERT INTO score_upper_bounds (seed, score_upper_bound, reason) "
|
||||
"VALUES (%s,%s,%s) "
|
||||
|
|
|
@ -157,7 +157,7 @@ class GreedyStrategy():
|
|||
hand_states = [[CardState(card_type(self.game_state, card), card, None) for card in self.game_state.hands[p]]
|
||||
for p in range(self.game_state.num_players)]
|
||||
|
||||
# find dupes in players hands, marke one card crit and the other one trash
|
||||
# find dupes in players hands, mark one card crit and the other one trash
|
||||
p = False
|
||||
for states in hand_states:
|
||||
counter = collections.Counter(map(lambda state: state.card, states))
|
||||
|
|
Loading…
Reference in a new issue