Store cert games in DB. Check for bottom/topdeck losses

This commit is contained in:
Maximilian Keßler 2024-11-12 13:58:49 +01:00
parent 3e5f727eda
commit 6ad728de4d
3 changed files with 135 additions and 8 deletions

View file

@ -93,7 +93,7 @@ def load_instance(seed: str) -> Optional[hanabi.live.hanab_live.HanabLiveInstanc
return hanabi.live.hanab_live.HanabLiveInstance(deck, num_players, var_id)
def load_game_parts(game_id: int) -> Tuple[hanabi.live.hanab_live.HanabLiveInstance, List[hanabi.hanab_game.Action]]:
def load_game_parts(game_id: int, cert_game: bool = False) -> Tuple[hanabi.live.hanab_live.HanabLiveInstance, List[hanabi.hanab_game.Action]]:
"""
Loads information on game from database
@param game_id: ID of game
@ -119,7 +119,7 @@ def load_game_parts(game_id: int) -> Tuple[hanabi.live.hanab_live.HanabLiveInsta
# Unpack results now
(num_players, seed, one_extra_card, one_less_card, deck_plays, all_or_nothing, clue_starved, variant_name, variant_id, throw_it_in_a_hole) = res
actions = load_actions(game_id)
actions = load_actions(game_id, cert_game)
deck = load_deck(seed)
instance = hanabi.live.hanab_live.HanabLiveInstance(
@ -136,8 +136,8 @@ def load_game_parts(game_id: int) -> Tuple[hanabi.live.hanab_live.HanabLiveInsta
return instance, actions
def load_game(game_id: int) -> hanabi.live.hanab_live.HanabLiveGameState:
instance, actions = load_game_parts(game_id)
def load_game(game_id: int, cert_game: bool = False) -> hanabi.live.hanab_live.HanabLiveGameState:
instance, actions = load_game_parts(game_id, cert_game)
game = hanabi.live.hanab_live.HanabLiveGameState(instance)
for action in actions:
game.make_action(action)

View file

@ -1,9 +1,13 @@
import hanabi.live.compress
from hanabi.hanab_game import DeckCard
from hanabi import database
from hanabi.live.variants import Variant
from hanabi.database import games_db_interface
import random
from src.hanabi.solvers.sat import solve_sat
def get_deck(variant: Variant):
deck = []
for suit_index, suit in enumerate(variant.suits):
@ -33,6 +37,20 @@ def generate_deck(variant: Variant, num_players: int, seed: int, seed_class: int
random.shuffle(deck)
return seed, deck
def link():
seed = "p5v0sunblinkingly-kobe-prescriptively"
deck = database.games_db_interface.load_deck(seed)
database.cur.execute("SELECT id FROM certificate_games WHERE seed = %s", (seed,))
(game_id, ) = database.cur.fetchone()
actions = database.games_db_interface.load_actions(game_id, True)
inst = hanabi.hanab_game.HanabiInstance(deck, 5)
game = hanabi.hanab_game.GameState(inst)
for action in actions:
game.make_action(action)
print(hanabi.live.compress.link(game))
def generate_decks_for_variant(variant_id: int, num_players: int, num_seeds: int, seed_class: int = 1):
variant = Variant.from_db(variant_id)
for seed_num in range(num_seeds):
@ -48,7 +66,8 @@ def generate_decks_for_variant(variant_id: int, num_players: int, num_seeds: int
def main():
database.global_db_connection_manager.read_config()
database.global_db_connection_manager.connect()
generate_decks_for_variant(0, 2, 100)
link()
# generate_decks_for_variant(0, 2, 100)
if __name__ == '__main__':
main()

View file

@ -1,14 +1,19 @@
import collections
from enum import Enum
from typing import List
from typing import List, Any, Optional, Tuple
from dataclasses import dataclass
import alive_progress
import hanabi.hanab_game
from hanabi import database
from hanabi import logger
from hanabi import hanab_game
from hanabi.hanab_game import DeckCard
from hanabi.live import compress
from hanabi.database import games_db_interface
class InfeasibilityType(Enum):
Pace = 0 # idx denotes index of last card drawn before being forced to reduce pace, value denotes how bad pace is
@ -21,11 +26,13 @@ class InfeasibilityType(Enum):
HandSizeWithSqueeze = 12
HandSizeWithBdr = 13
HandSizeWithBdrSqueeze = 14
BottomTopDeck = 20
# further reasons, currently not scanned for
BottomTopDeck = 20
DoubleBottomTopDeck = 30
CritAtBottom = 40
# Default reason when we have nothing else
SAT = 50
@ -46,6 +53,77 @@ class InfeasibilityReason:
return "Critical non-5 at bottom"
def generate_all_choices(l: List[List[Any]]):
if len(l) == 0:
yield []
return
head, *tail = l
for option in head:
for back in generate_all_choices(tail):
yield [option] + back
def check_for_top_bottom_deck_loss(instance: hanab_game.HanabiInstance) -> bool:
hands = [instance.deck[p * instance.hand_size : (p+1) * instance.hand_size] for p in range(instance.num_players)]
# scan the deck in reverse order if any card is forced to be late
found = {}
# Note that only the last 4 cards are relevant for single-suit distribution loss
for i, card in enumerate(reversed(instance.deck[-4:])):
if card in found.keys():
found[card] += 1
else:
found[card] = 1
if found[card] >= 3 or (card.rank != 1 and found[card] >= 2):
max_rank_starting_extra_round = card.rank + (instance.deck_size - card.deck_index - 2)
# Next, need to figure out what positions of cards of the same suit are fixed
positions_by_rank = [[] for _ in range(6)]
for rank in range(max_rank_starting_extra_round, 6):
for player, hand in enumerate(hands):
card_test = DeckCard(card.suitIndex, rank)
for card_hand in hand:
if card_test == card_hand:
positions_by_rank[rank].append(player)
# clean up where we have free choice anyway
for rank, positions in enumerate(positions_by_rank):
if rank != 5 and len(positions) < 2:
positions.clear()
if len(positions) == 0:
positions.append(None)
# Now, iterate through all choices in starting hands (None stands for free choice of a card) and check them
assignment_found = False
for assignment in generate_all_choices(positions_by_rank):
cur_player = None
num_turns = 0
for rank in range(max_rank_starting_extra_round, 6):
if cur_player is None or assignment[rank] is None:
num_turns += 1
else:
# Note the -1 and +1 to output things in range [1,5] instead of [0,4]
num_turns += (assignment[rank] - cur_player - 1) % instance.num_players + 1
if assignment[rank] is not None:
cur_player = assignment[rank]
elif cur_player is not None:
cur_player = (cur_player + 1) % instance.num_players
if num_turns <= instance.num_players + 1:
assignment_found = True
# If no assignment worked out, the deck is infeasible because of this suit
if not assignment_found:
return True
# If we reach this point, we checked for every card near the bottom of the deck and found a possible endgame each
return False
def analyze(instance: hanab_game.HanabiInstance, only_find_first=False) -> List[InfeasibilityReason]:
"""
@ -65,6 +143,12 @@ def analyze(instance: hanab_game.HanabiInstance, only_find_first=False) -> List[
"""
reasons = []
top_bottom_deck_loss = check_for_top_bottom_deck_loss(instance)
if top_bottom_deck_loss:
reasons.append(InfeasibilityReason(InfeasibilityType.BottomTopDeck))
if only_find_first:
return reasons
# 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:
@ -133,7 +217,8 @@ def analyze(instance: hanab_game.HanabiInstance, only_find_first=False) -> List[
artificial_crits.add(filtered_deck[-2])
# Last card in the deck can never be played
artificial_crits.add(filtered_deck[-1])
if instance.deck[-1].rank != 5:
artificial_crits.add(instance.deck[-1])
for (i, card) in enumerate(instance.deck):
if card.rank == stacks[card.suitIndex] + 1:
@ -240,3 +325,26 @@ def run_on_database(variant_id):
)
bar()
database.conn.commit()
def main():
seed = "p5v0sporcupines-underclass-phantasmagorical"
seed = 'p5c1s98804'
seed = 'p4c1s1116'
seed = 'p5c1s14459'
num_players = 5
database.global_db_connection_manager.read_config()
database.global_db_connection_manager.connect()
database.cur.execute("SELECT seed, num_players FROM seeds WHERE (feasible IS NULL OR feasible = false) AND class = 1 AND num_players = 5")
# for (seed, num_players) in database.cur.fetchall():
for _ in range(1):
deck = database.games_db_interface.load_deck(seed)
inst = hanabi.hanab_game.HanabiInstance(deck, num_players)
lost = check_for_top_bottom_deck_loss(inst)
if lost:
print(seed)
if __name__ == "__main__":
main()