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()
|
retval = SolutionData()
|
||||||
# first, sanity check on running out of pace
|
# first, sanity check on running out of pace
|
||||||
result = deck_analyzer.analyze(instance)
|
result = deck_analyzer.analyze(instance)
|
||||||
if len(result) != 0:
|
if len(result.infeasibility_reasons) != 0:
|
||||||
logger.verbose("found infeasible deck by preliminary analysis")
|
logger.verbose("found infeasible deck by preliminary analysis")
|
||||||
retval.feasible = False
|
retval.feasible = False
|
||||||
retval.infeasibility_reasons = result
|
retval.infeasibility_reasons = result.infeasibility_reasons
|
||||||
return retval
|
return retval
|
||||||
for num_remaining_cards in [0, 10, 20]:
|
for num_remaining_cards in [0, 10, 20]:
|
||||||
# logger.info("trying with {} remaining cards".format(num_remaining_cards))
|
# logger.info("trying with {} remaining cards".format(num_remaining_cards))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import collections
|
import collections
|
||||||
|
import dataclasses
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Any, Optional, Tuple, Set
|
from typing import List, Any, Optional, Tuple, Set
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
@ -139,8 +140,30 @@ def analyze_2p_bottom_loss(instance: hanab_game.HanabiInstance) -> List[Infeasib
|
||||||
return reasons
|
return reasons
|
||||||
|
|
||||||
|
|
||||||
def analyze_pace_and_hand_size(instance: hanab_game.HanabiInstance, do_squeeze: bool = True) -> List[InfeasibilityReason]:
|
@dataclass
|
||||||
reasons = []
|
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 will sweep through the deck and pretend that
|
||||||
# - we keep all non-trash cards in our hands
|
# - we keep all non-trash cards in our hands
|
||||||
# - we instantly play all playable cards as soon as we have them
|
# - 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
|
squeeze = False
|
||||||
artificial_crits = set()
|
artificial_crits = set()
|
||||||
|
|
||||||
|
|
||||||
# Investigate BDRs. This catches special cases of Pace losses in 2p, as well as mark some cards critical because
|
# 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.
|
# their second copies cannot be used.
|
||||||
filtered_deck = [card for card in instance.deck if card.rank != 5]
|
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
|
# Use a bool flag to only mark this reason once
|
||||||
if hand_size_left_for_crits < 0 and not hand_size_found:
|
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
|
hand_size_found = True
|
||||||
|
|
||||||
# the last - 1 is there because we have to discard 'next', causing a further draw
|
# 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 cur_pace < 0 and not pace_found:
|
||||||
if squeeze:
|
if squeeze:
|
||||||
# We checked single-suit pace losses beforehand (which can only occur in 2p)
|
# 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:
|
else:
|
||||||
reasons.append(InfeasibilityReason(InfeasibilityType.Pace, card_index))
|
reasons.infeasibility_reasons.append(InfeasibilityReason(InfeasibilityType.Pace, card_index))
|
||||||
|
|
||||||
pace_found = True
|
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
|
return reasons
|
||||||
|
|
||||||
|
|
||||||
def analyze(instance: hanab_game.HanabiInstance) -> List[InfeasibilityReason]:
|
def analyze(instance: hanab_game.HanabiInstance) -> AnalysisResult:
|
||||||
reasons: List[InfeasibilityReason] = []
|
# 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 losses in a single suit.
|
||||||
top_bottom_deck_loss = check_for_top_bottom_deck_loss(instance)
|
top_bottom_deck_loss = check_for_top_bottom_deck_loss(instance)
|
||||||
if top_bottom_deck_loss is not None:
|
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
|
# 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
|
# check for critical non-fives at bottom of the deck
|
||||||
bottom_card = instance.deck[-1]
|
bottom_card = instance.deck[-1]
|
||||||
if bottom_card.rank != 5 and bottom_card.suitIndex in instance.dark_suits:
|
if bottom_card.rank != 5 and bottom_card.suitIndex in instance.dark_suits:
|
||||||
reasons.append(InfeasibilityReason(
|
result.infeasibility_reasons.append(InfeasibilityReason(
|
||||||
InfeasibilityType.CritAtBottom,
|
InfeasibilityType.CritAtBottom,
|
||||||
instance.deck_size - 1
|
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
|
# 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):
|
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:
|
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:
|
for (seed, num_players, deck_str) in res:
|
||||||
deck = compress.decompress_deck(deck_str)
|
deck = compress.decompress_deck(deck_str)
|
||||||
reasons = analyze(hanab_game.HanabiInstance(deck, num_players))
|
result = analyze(hanab_game.HanabiInstance(deck, num_players))
|
||||||
for reason in reasons:
|
for reason in result.infeasibility_reasons:
|
||||||
database.cur.execute(
|
database.cur.execute(
|
||||||
"INSERT INTO score_upper_bounds (seed, score_upper_bound, reason) "
|
"INSERT INTO score_upper_bounds (seed, score_upper_bound, reason) "
|
||||||
"VALUES (%s,%s,%s) "
|
"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]]
|
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)]
|
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
|
p = False
|
||||||
for states in hand_states:
|
for states in hand_states:
|
||||||
counter = collections.Counter(map(lambda state: state.card, states))
|
counter = collections.Counter(map(lambda state: state.card, states))
|
||||||
|
|
Loading…
Reference in a new issue