2023-05-06 23:54:26 +02:00
|
|
|
import copy
|
2023-07-04 18:10:37 +02:00
|
|
|
from typing import Tuple
|
2023-05-06 23:54:26 +02:00
|
|
|
|
2023-07-04 18:53:18 +02:00
|
|
|
from hanabi.database import conn
|
2023-07-04 18:52:59 +02:00
|
|
|
from hanabi.live.compress import decompress_deck, decompress_actions, link
|
2023-07-04 18:53:18 +02:00
|
|
|
from hanabi.game import GameState
|
2023-07-04 18:52:59 +02:00
|
|
|
from hanabi.live.hanab_live import HanabLiveInstance, HanabLiveGameState
|
2023-07-04 18:53:18 +02:00
|
|
|
from hanabi.solvers.sat import solve_sat
|
|
|
|
from hanabi import logger
|
2023-05-06 23:54:26 +02:00
|
|
|
|
|
|
|
|
2023-05-20 14:32:42 +02:00
|
|
|
# returns minimal number T of turns (from game) after which instance was infeasible
|
|
|
|
# and a replay achieving maximum score while following the replay for the first (T-1) turns:
|
|
|
|
# if instance is feasible, returns number of turns + 1
|
|
|
|
# returns 0 if instance is infeasible
|
|
|
|
# returns 1 if instance is feasible but first turn is suboptimal
|
2023-05-06 23:54:26 +02:00
|
|
|
# ...
|
2023-05-20 14:32:42 +02:00
|
|
|
# # turns + 1 if the final state is still winning
|
2023-05-06 23:54:26 +02:00
|
|
|
def check_game(game_id: int) -> Tuple[int, GameState]:
|
2023-05-20 14:32:42 +02:00
|
|
|
logger.debug("Analysing game {}".format(game_id))
|
2023-05-06 23:54:26 +02:00
|
|
|
with conn.cursor() as cur:
|
|
|
|
cur.execute("SELECT games.num_players, deck, actions, score, games.variant_id FROM games "
|
|
|
|
"INNER JOIN seeds ON seeds.seed = games.seed "
|
|
|
|
"WHERE games.id = (%s)",
|
|
|
|
(game_id,)
|
|
|
|
)
|
2023-05-14 19:10:41 +02:00
|
|
|
res = cur.fetchone()
|
|
|
|
if res is None:
|
|
|
|
raise ValueError("No game associated with id {} in database.".format(game_id))
|
|
|
|
(num_players, compressed_deck, compressed_actions, score, variant_id) = res
|
2023-05-06 23:54:26 +02:00
|
|
|
deck = decompress_deck(compressed_deck)
|
|
|
|
actions = decompress_actions(compressed_actions)
|
|
|
|
|
2023-05-13 18:26:06 +02:00
|
|
|
instance = HanabLiveInstance(deck, num_players, variant_id=variant_id)
|
2023-05-06 23:54:26 +02:00
|
|
|
|
2023-07-04 18:10:37 +02:00
|
|
|
# check if the instance is already won
|
2023-05-06 23:54:26 +02:00
|
|
|
if instance.max_score == score:
|
2023-05-20 14:32:42 +02:00
|
|
|
game = HanabLiveGameState(instance)
|
|
|
|
for action in actions:
|
|
|
|
game.make_action(action)
|
2023-05-06 23:54:26 +02:00
|
|
|
# instance has been won, nothing to compute here
|
2023-05-20 14:32:42 +02:00
|
|
|
return len(actions) + 1, game
|
2023-05-06 23:54:26 +02:00
|
|
|
|
|
|
|
# first, check if the instance itself is feasible:
|
2023-05-13 18:26:06 +02:00
|
|
|
game = HanabLiveGameState(instance)
|
2023-05-06 23:54:26 +02:00
|
|
|
solvable, solution = solve_sat(game)
|
|
|
|
if not solvable:
|
2023-05-09 17:44:37 +02:00
|
|
|
return 0, solution
|
2023-05-20 14:32:42 +02:00
|
|
|
logger.verbose("Instance {} is feasible after 0 turns: {}".format(game_id, link(solution)))
|
2023-05-06 23:54:26 +02:00
|
|
|
|
2023-07-04 18:10:37 +02:00
|
|
|
# store lower and upper bounds of numbers of turns after which we know the game was feasible / infeasible
|
|
|
|
solvable_turn = 0
|
|
|
|
unsolvable_turn = len(actions)
|
|
|
|
|
2023-05-13 18:26:06 +02:00
|
|
|
while unsolvable_turn - solvable_turn > 1:
|
2023-05-06 23:54:26 +02:00
|
|
|
try_turn = (unsolvable_turn + solvable_turn) // 2
|
|
|
|
try_game = copy.deepcopy(game)
|
2023-07-04 18:10:37 +02:00
|
|
|
assert len(try_game.actions) == solvable_turn
|
2023-05-06 23:54:26 +02:00
|
|
|
for a in range(solvable_turn, try_turn):
|
|
|
|
try_game.make_action(actions[a])
|
2023-05-20 14:38:38 +02:00
|
|
|
logger.debug("Checking if instance {} is feasible after {} turns.".format(game_id, try_turn))
|
2023-05-06 23:54:26 +02:00
|
|
|
solvable, potential_sol = solve_sat(try_game)
|
|
|
|
if solvable:
|
|
|
|
solution = potential_sol
|
|
|
|
game = try_game
|
|
|
|
solvable_turn = try_turn
|
2023-05-20 14:38:38 +02:00
|
|
|
logger.verbose("Instance {} is feasible after {} turns: {}#{}"
|
|
|
|
.format(game_id, solvable_turn, link(solution), solvable_turn + 1))
|
2023-05-06 23:54:26 +02:00
|
|
|
else:
|
|
|
|
unsolvable_turn = try_turn
|
2023-05-20 14:38:38 +02:00
|
|
|
logger.verbose("Instance {} is not feasible after {} turns.".format(game_id, unsolvable_turn))
|
2023-05-06 23:54:26 +02:00
|
|
|
|
2023-07-04 18:10:37 +02:00
|
|
|
assert unsolvable_turn - 1 == solvable_turn
|
2023-05-06 23:54:26 +02:00
|
|
|
return unsolvable_turn, solution
|