Store cert games in DB. Check for bottom/topdeck losses
This commit is contained in:
parent
3e5f727eda
commit
6ad728de4d
3 changed files with 135 additions and 8 deletions
|
@ -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)
|
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
|
Loads information on game from database
|
||||||
@param game_id: ID of game
|
@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
|
# 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
|
(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)
|
deck = load_deck(seed)
|
||||||
|
|
||||||
instance = hanabi.live.hanab_live.HanabLiveInstance(
|
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
|
return instance, actions
|
||||||
|
|
||||||
|
|
||||||
def load_game(game_id: int) -> hanabi.live.hanab_live.HanabLiveGameState:
|
def load_game(game_id: int, cert_game: bool = False) -> hanabi.live.hanab_live.HanabLiveGameState:
|
||||||
instance, actions = load_game_parts(game_id)
|
instance, actions = load_game_parts(game_id, cert_game)
|
||||||
game = hanabi.live.hanab_live.HanabLiveGameState(instance)
|
game = hanabi.live.hanab_live.HanabLiveGameState(instance)
|
||||||
for action in actions:
|
for action in actions:
|
||||||
game.make_action(action)
|
game.make_action(action)
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
import hanabi.live.compress
|
||||||
from hanabi.hanab_game import DeckCard
|
from hanabi.hanab_game import DeckCard
|
||||||
from hanabi import database
|
from hanabi import database
|
||||||
from hanabi.live.variants import Variant
|
from hanabi.live.variants import Variant
|
||||||
from hanabi.database import games_db_interface
|
from hanabi.database import games_db_interface
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from src.hanabi.solvers.sat import solve_sat
|
||||||
|
|
||||||
|
|
||||||
def get_deck(variant: Variant):
|
def get_deck(variant: Variant):
|
||||||
deck = []
|
deck = []
|
||||||
for suit_index, suit in enumerate(variant.suits):
|
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)
|
random.shuffle(deck)
|
||||||
return seed, 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):
|
def generate_decks_for_variant(variant_id: int, num_players: int, num_seeds: int, seed_class: int = 1):
|
||||||
variant = Variant.from_db(variant_id)
|
variant = Variant.from_db(variant_id)
|
||||||
for seed_num in range(num_seeds):
|
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():
|
def main():
|
||||||
database.global_db_connection_manager.read_config()
|
database.global_db_connection_manager.read_config()
|
||||||
database.global_db_connection_manager.connect()
|
database.global_db_connection_manager.connect()
|
||||||
generate_decks_for_variant(0, 2, 100)
|
link()
|
||||||
|
# generate_decks_for_variant(0, 2, 100)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
|
import collections
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List
|
from typing import List, Any, Optional, Tuple
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import alive_progress
|
import alive_progress
|
||||||
|
|
||||||
|
import hanabi.hanab_game
|
||||||
from hanabi import database
|
from hanabi import database
|
||||||
from hanabi import logger
|
from hanabi import logger
|
||||||
from hanabi import hanab_game
|
from hanabi import hanab_game
|
||||||
|
from hanabi.hanab_game import DeckCard
|
||||||
from hanabi.live import compress
|
from hanabi.live import compress
|
||||||
|
|
||||||
|
from hanabi.database import games_db_interface
|
||||||
|
|
||||||
|
|
||||||
class InfeasibilityType(Enum):
|
class InfeasibilityType(Enum):
|
||||||
Pace = 0 # idx denotes index of last card drawn before being forced to reduce pace, value denotes how bad pace is
|
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
|
HandSizeWithSqueeze = 12
|
||||||
HandSizeWithBdr = 13
|
HandSizeWithBdr = 13
|
||||||
HandSizeWithBdrSqueeze = 14
|
HandSizeWithBdrSqueeze = 14
|
||||||
|
BottomTopDeck = 20
|
||||||
|
|
||||||
# further reasons, currently not scanned for
|
# further reasons, currently not scanned for
|
||||||
BottomTopDeck = 20
|
|
||||||
DoubleBottomTopDeck = 30
|
DoubleBottomTopDeck = 30
|
||||||
CritAtBottom = 40
|
CritAtBottom = 40
|
||||||
|
|
||||||
|
# Default reason when we have nothing else
|
||||||
SAT = 50
|
SAT = 50
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,6 +53,77 @@ class InfeasibilityReason:
|
||||||
return "Critical non-5 at bottom"
|
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]:
|
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 = []
|
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
|
# 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:
|
||||||
|
@ -133,7 +217,8 @@ def analyze(instance: hanab_game.HanabiInstance, only_find_first=False) -> List[
|
||||||
artificial_crits.add(filtered_deck[-2])
|
artificial_crits.add(filtered_deck[-2])
|
||||||
|
|
||||||
# Last card in the deck can never be played
|
# 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):
|
for (i, card) in enumerate(instance.deck):
|
||||||
if card.rank == stacks[card.suitIndex] + 1:
|
if card.rank == stacks[card.suitIndex] + 1:
|
||||||
|
@ -240,3 +325,26 @@ def run_on_database(variant_id):
|
||||||
)
|
)
|
||||||
bar()
|
bar()
|
||||||
database.conn.commit()
|
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()
|
Loading…
Reference in a new issue