From d91dec3c81fadc602da65a806e9bab3674511589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Sat, 6 May 2023 23:54:26 +0200 Subject: [PATCH] add method to check replay for last feasible state (binary search) --- check_game.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 check_game.py diff --git a/check_game.py b/check_game.py new file mode 100644 index 0000000..b939794 --- /dev/null +++ b/check_game.py @@ -0,0 +1,70 @@ +import copy +from typing import Tuple, Optional + +from database import conn +from compress import decompress_deck, decompress_actions, compress_actions, link +from hanabi import Action, HanabiInstance, GameState +from sat import solve_sat +from download_data import export_game + + +# returns number of first turn before which the game was infeasible (counting from 0) +# and a replay achieving maximum score (from this turn onwards) if instance is feasible +# 0 if instance is infeasible +# 1 if instance is feasible but first turn is suboptimal +# ... +# (number of turns) if replay was winning +def check_game(game_id: int) -> Tuple[int, GameState]: + 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,) + ) + (num_players, compressed_deck, compressed_actions, score, variant_id) = cur.fetchone() + deck = decompress_deck(compressed_deck) + actions = decompress_actions(compressed_actions) + + instance = HanabiInstance(deck, num_players, variant_id=variant_id) + + if instance.max_score == score: + # instance has been won, nothing to compute here + return len(actions) + + # store the turn numbers *before* we know the game was (in)feasible + solvable_turn = 0 + unsolvable_turn = len(actions) + + # first, check if the instance itself is feasible: + game = GameState(instance) + solvable, solution = solve_sat(game) + if not solvable: + return -1, solution + + while (unsolvable_turn - solvable_turn > 1): + try_turn = (unsolvable_turn + solvable_turn) // 2 + try_game = copy.deepcopy(game) + assert(len(try_game.actions) == solvable_turn) + for a in range(solvable_turn, try_turn): + try_game.make_action(actions[a]) + solvable, potential_sol = solve_sat(try_game) + if solvable: + solution = potential_sol + game = try_game + solvable_turn = try_turn + else: + unsolvable_turn = try_turn + + assert(unsolvable_turn - 1 == solvable_turn) + return unsolvable_turn, solution + +if __name__ == "__main__": + game_id = 961092 + export_game(game_id) + print("checking game {}".format(game_id)) + turn, sol = check_game(game_id) + if turn != 0: + print(turn, link(sol)) + else: + print("instance is unfeasible") + pass