Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.