This commit is contained in:
Maximilian Keßler 2023-05-11 15:15:15 +02:00
parent a715bda02c
commit 3cc49b4813
Signed by: max
GPG key ID: BCC5A619923C0BA5
4 changed files with 64 additions and 35 deletions

View file

@ -59,7 +59,7 @@ def check_game(game_id: int) -> Tuple[int, GameState]:
return unsolvable_turn, solution return unsolvable_turn, solution
if __name__ == "__main__": if __name__ == "__main__":
game_id = 961092 game_id = 963339
export_game(game_id) export_game(game_id)
print("checking game {}".format(game_id)) print("checking game {}".format(game_id))
turn, sol = check_game(game_id) turn, sol = check_game(game_id)

View file

@ -85,12 +85,12 @@ class HanabiInstance():
self, self,
deck: List[DeckCard], # assumes a default deck, every suit has to be distributed either [1,1,1,2,2,3,3,4,4,5] or [1,2,3,4,5] deck: List[DeckCard], # assumes a default deck, every suit has to be distributed either [1,1,1,2,2,3,3,4,4,5] or [1,2,3,4,5]
num_players: int, # number of players that play this deck, in range [2,6] num_players: int, # number of players that play this deck, in range [2,6]
hand_size: Optional[int] = None, # number of cards that each player holds hand_size: Optional[int] = None, # number of cards that each player holds
num_strikes: Optional[int] = None, # number of strikes that leads to game loss num_strikes: Optional[int] = None, # number of strikes that leads to game loss
clue_starved: bool = False, # if true, discarding and playing fives only gives back half a clue clue_starved: bool = False, # if true, discarding and playing fives only gives back half a clue
variant_id: Optional[int] = None # optional: variant id of hanab.live, useful if instance gets exported to be viewed in browser fives_give_clue: bool = True, # if false, then playing a five will not change the clue count
): ):
assert(2 <= num_players <= 6)
# defining properties # defining properties
self.deck = deck self.deck = deck
@ -98,6 +98,7 @@ class HanabiInstance():
self.hand_size = hand_size or constants.HAND_SIZES[self.num_players] self.hand_size = hand_size or constants.HAND_SIZES[self.num_players]
self.num_strikes = num_strikes or constants.NUM_STRIKES self.num_strikes = num_strikes or constants.NUM_STRIKES
self.clue_starved = clue_starved self.clue_starved = clue_starved
self.fives_give_clue = fives_give_clue
# normalize deck indices # normalize deck indices
for (idx, card) in enumerate(self.deck): for (idx, card) in enumerate(self.deck):
@ -119,9 +120,6 @@ class HanabiInstance():
+ 8 + (self.num_suits - 1) \ + 8 + (self.num_suits - 1) \
+ (-1 if self.num_players >= 5 else 0) + (-1 if self.num_players >= 5 else 0)
# TODO: set a meaningful default here for export?
self._variant_id: Optional[int] = variant_id
@property @property
def num_dealt_cards(self): def num_dealt_cards(self):
return self.num_players * self.hand_size return self.num_players * self.hand_size
@ -130,15 +128,6 @@ class HanabiInstance():
def draw_pile_size(self): def draw_pile_size(self):
return self.deck_size - self.num_dealt_cards return self.deck_size - self.num_dealt_cards
@property
def variant_id(self):
if self._variant_id is not None:
return self._variant_id
else:
# ensure no key error can happen
assert(self.is_standard())
return constants.VARIANT_IDS_STANDARD_DISTRIBUTIONS[self.num_suits][self.num_dark_suits]
@property @property
def max_score(self): def max_score(self):
return 5 * self.num_suits return 5 * self.num_suits
@ -147,21 +136,6 @@ class HanabiInstance():
def clue_increment(self): def clue_increment(self):
return 0.5 if self.clue_starved else 1 return 0.5 if self.clue_starved else 1
# returns True if the instance has values matching hanabi-live rules
# (i.e. standard + extra variants with 5 / 6 suits)
def is_standard(self):
return all([
2 <= self.num_players <= 6,
self.hand_size == constants.HAND_SIZES[self.num_players],
self.num_strikes == constants.NUM_STRIKES,
3 <= self.num_suits <= 6,
0 <= self.num_dark_suits <= 2,
4 <= self.num_suits - self.num_dark_suits or self.num_suits == 3
# TODO: check that variant id matches deck distribution
]
)
class GameState(): class GameState():
def __init__(self, instance: HanabiInstance): def __init__(self, instance: HanabiInstance):
@ -210,7 +184,7 @@ class GameState():
card = self.instance.deck[card_idx] card = self.instance.deck[card_idx]
if card.rank == self.stacks[card.suitIndex] + 1: if card.rank == self.stacks[card.suitIndex] + 1:
self.stacks[card.suitIndex] += 1 self.stacks[card.suitIndex] += 1
if card.rank == 5 and self.clues != 8: if card.rank == 5 and self.clues != 8 and self.fives_give_clue:
self.clues += self.instance.clue_increment self.clues += self.instance.clue_increment
else: else:
self.strikes += 1 self.strikes += 1

6
sat.py
View file

@ -304,7 +304,7 @@ def solve_sat(starting_state: GameState | HanabiInstance) -> Tuple[bool, Optiona
model = get_model(constraints) model = get_model(constraints)
if model: if model:
# print_model(model, game_state, ls) print_model(model, game_state, ls)
solution = evaluate_model(model, copy.deepcopy(game_state), ls) solution = evaluate_model(model, copy.deepcopy(game_state), ls)
return True, solution return True, solution
else: else:
@ -355,11 +355,11 @@ def evaluate_model(model, cur_game_state: GameState, ls: Literals) -> GameState:
def run_deck(): def run_deck():
puzzle = False puzzle = True
if puzzle: if puzzle:
deck_str = 'p5 p3 b4 r5 y4 y4 y5 r4 b2 y2 y3 g5 g2 g3 g4 p4 r3 b2 b3 b3 p4 b1 p2 b1 b1 p2 p1 p1 g1 r4 g1 r1 r3 r1 g1 r1 p1 b4 p3 g2 g3 g4 b5 y1 y1 y1 r2 r2 y2 y3' deck_str = 'p5 p3 b4 r5 y4 y4 y5 r4 b2 y2 y3 g5 g2 g3 g4 p4 r3 b2 b3 b3 p4 b1 p2 b1 b1 p2 p1 p1 g1 r4 g1 r1 r3 r1 g1 r1 p1 b4 p3 g2 g3 g4 b5 y1 y1 y1 r2 r2 y2 y3'
deck = [DeckCard(COLORS.index(c[0]), int(c[1])) for c in deck_str.split(" ")] deck = [DeckCard(COLOR_INITIALS.index(c[0]), int(c[1])) for c in deck_str.split(" ")]
num_p = 5 num_p = 5
else: else:
deck_str = "15gfvqluvuwaqnmrkpkaignlaxpjbmsprksfcddeybfixchuhtwo" deck_str = "15gfvqluvuwaqnmrkpkaignlaxpjbmsprksfcddeybfixchuhtwo"

View file

@ -13,3 +13,58 @@ def variant_name(variant_id):
def num_suits(variant_id): def num_suits(variant_id):
return next(len(var['suits']) for var in VARIANTS if var['id'] == variant_id) return next(len(var['suits']) for var in VARIANTS if var['id'] == variant_id)
def properties(variant_id):
return next(var for var in VARIANTS if var['id'] == variant_id)
if __name__ == "__main__":
x = set()
c = set()
for var in VARIANTS:
for k in var.keys():
x.add(k)
for s in var['suits']:
c.add(s)
for y in x:
print(y)
for s in c:
print(s)
# need: suit name -> colors
"""
# actual changes of theoretical instance
clueStarved
throwItInHole (no clues for fives)
# general restrictions on what clues are allowed
alternatingClues
clueColors
clueRanks
synesthesia (no rank clused, but color touches rank as well)
# can be ignored
cowPig
duck
# -> use oracle?
# clue touch changed
chimneys
funnels
colorCluesTouchNothing
rankCluesTouchNothing
oddsAndEvens (ranks touch ranks of same parity)
# changes behaviour of ones or fives
specialAllClueColors
specialAllClueRanks
specialNoClueColors
specialNoClueRanks
specialDeceptive
specialRank
upOrDown
criticalFours
"""