{{ message }}

Instantly share code, notes, and snippets.

# bob312123/doors_guards_keys_puzzle.py

Created Nov 25, 2019
 from typing import Callable class Guard: """ Represents one of the guards in https://puzzling.stackexchange.com/questions/91459/two-doors-two-guards-and-two-keys What differentiates guards are whether or not they tell the truth, whether or not they guard the freedom door, and whether or not they have the key to the freedom door """ def __init__(self, tells_truth: bool, guards_freedom: bool, has_freedom_key: bool): """ Initializes a new Guard with the following properties: :param tells_truth: True if the guard always tells the truth and False if the guard always lies :param guards_freedom: True if the guard is guarding the freedom door and False if it does not :param has_freedom_key: True if the guard has the key to the freedom door and False if it does not """ self.tells_truth = tells_truth self.guards_freedom = guards_freedom self.has_freedom_key = has_freedom_key def __repr__(self): """ Returns a string representation of this guard :return: A string representation of this guard """ return self.__str__() def __str__(self): """ Returns a string representation of this guard with T/L as truth-telling or lying, F/D as freedom door or death, and H/N as holding key or not :return: A string representation of this guard """ return f"Guard({'T' if self.tells_truth else 'L'}{'F' if self.guards_freedom else 'D'}{'H' if self.has_freedom_key else 'N'})" def query(guard: Guard, possibility: bool, first_negative: bool, second_negative: bool, third_negative: bool, operator_one: Callable, operator_two: Callable): """ Returns true if the overall boolean expression is true where, if we let Q be possibility or the assumption for the question, G be if the guard being asked is guarding the freedom door, K be if the guard being asked has the key to the freedom door, A, B, and C, each be the negation status of the previous three, J be operator one, and K be operator two, then we have the following expression: Q == K(BG, J(AQ, CK)) :param guard: The current guard being asked :param possibility: True if we are making the assumption that the overall query is True and False if we assume the overall query is False :param first_negative: True if we wish to negate the guard having freedom :param second_negative: True if we wish to negate the internal self-reference to the overall query :param third_negative: True if we wish to negate the guard having the key to the freedom door :param operator_one: The operation between the possibility and key parts :param operator_two: The operation between the guarding part and the result of operation one :return: True if the overall boolean expression is true """ guarding_part = guard.guards_freedom possibility_part = possibility key_part = guard.has_freedom_key if first_negative: guarding_part = not guarding_part if second_negative: possibility_part = not possibility_part if third_negative: key_part = not key_part return possibility == operator_two(guarding_part, operator_one(possibility_part, key_part)) def main() -> None: """ The main function for attempting to find a solution to https://puzzling.stackexchange.com/questions/91459/two-doors-two-guards-and-two-keys :return: None """ # All possible variations of guards possible_guards = [] # 8 possible combinations of guards in three-bit combinations for i in range(8): # Convert the current combination number into a three-digit binary string guard_combo = bin(i)[2:].zfill(3) possible_guards.append(Guard(guard_combo[0] == '1', guard_combo[1] == '1', guard_combo[2] == '1')) possible_guards.reverse() # Possible operations to consider in the boolean expression operators = [bool.__eq__, bool.__and__, bool.__or__, bool.__xor__, lambda first, second: not first or second] operator_names = ["Equals", "And", "Or", "Xor", "Implies"] # Three possible operators for operator_one_index, operator_one in enumerate(operators): for operator_two_index, operator_two in enumerate(operators): # Eight possible options to negate in three-bit combinations for negation_num in range(8): # Convert the current combination number into a three-digit binary string negate_combo = bin(negation_num)[2:].zfill(3) negate_first = negate_combo[0] == "1" negate_second = negate_combo[1] == "1" negate_third = negate_combo[2] == "1" # The reponses given by each of the guards guard_responses = [] # Gets the number of each type of response num_each_response = {"SCREECH": 0, "MUTE": 0, "YES": 0, "NO": 0} # For each of the possible types of guards, add each of their responses and count the number of each type of response for guard in possible_guards: possibility_yes = query(guard, True, negate_first, negate_second, negate_third, operator_one, operator_two) possibility_no = query(guard, False, negate_first, negate_second, negate_third, operator_one, operator_two) if possibility_yes == True and possibility_no == True: guard_responses.append("SCREECH") elif possibility_yes == True: guard_responses.append("YES") elif possibility_no == True: guard_responses.append("NO") else: guard_responses.append("MUTE") num_each_response[guard_responses[-1]] += 1 # If each response is used twice and there are no differences between the truth-telling guard and lying guard in responses if all(value == 2 for value in num_each_response.values()) and all( guard_responses[i] == guard_responses[i + 4] for i in range(4)): # Then, we have a winning combination, so print it print(f"First negated? {negate_first}") print(f"Second negated? {negate_second}") print(f"Third negated? {negate_third}") print(f"Operation One? {operator_names[operator_one_index]}") print(f"Operation Two? {operator_names[operator_two_index]}") print(list(zip(possible_guards, guard_responses))) print() if __name__ == '__main__': main()