Last active
December 14, 2022 18:02
-
-
Save thirdratecyberpunk/55c91f1be163bc2bea03ec7617c2a54b to your computer and use it in GitHub Desktop.
Quick and dirty code for the automatable puzzles in the gchq Christmas Card puzzle 2022
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import nltk | |
from nltk.corpus import words | |
class Gear: | |
def __init__(self, word, direction): | |
# letters on the teeth, assuming the first character in the string is the initial gear setting | |
# and you read the letters in a clockwise sequence | |
self.word = word | |
# whether the gear rotates anti-clockwise (1) or clockwise (-1) | |
self.direction = direction | |
def rotate(self, num_steps): | |
""" | |
Returns the character that the gear should "rotate" to given a number of steps | |
""" | |
increments = (num_steps % len(self.word)) * self.direction | |
return self.word[increments] | |
def analysis_problem(): | |
""" | |
Finds all the even length words in the sentence | |
""" | |
message = "The Director has written his usual Christmas message but now uncover the odd one out amongst the words forming these two sentences Its odd because its not odd" | |
tokens = message.split() | |
even = [] | |
for token in tokens: | |
if len(token) % 2 == 0: | |
even.append(token) | |
return even | |
def coding_problem(): | |
""" | |
Brute forces all the possible solutions to the grid problem | |
Checks if all three rows are valid 3 letter word rows and then if the final state creates a 9 letter word | |
""" | |
# get all words | |
nltk.download('words') | |
word_list = words.words() | |
# initial problem constraints | |
initial_state = [["f", "o", "r"], ["m", "a", "t"], ["i", "o", "n"]] | |
blue_word = "part" | |
green_word = "eyes" | |
gold_word = "uncurl" | |
# finds all the valid words for row 0 | |
print("checking row 0...") | |
row_0_valid_words = [] | |
for gold_character in gold_word: | |
for blue_character in blue_word: | |
row_0 = initial_state[0] | |
row_0[0] = gold_character | |
row_0[1] = blue_character | |
result = ''.join(row_0) | |
if result in words.words(): | |
row_0_valid_words.append(result) | |
unique_row_0 = set(row_0_valid_words) | |
print(f"{len(unique_row_0)} unique solutions found: {unique_row_0}") | |
# finds all the valid words for row 1 | |
print("checking row 1...") | |
row_1_valid_words = [] | |
for gold_character in gold_word: | |
for blue_character in blue_word: | |
for green_character in green_word: | |
row_1 = initial_state[1] | |
row_1[0] = blue_character | |
row_1[1] = green_character | |
row_1[2] = gold_character | |
result = ''.join(row_1) | |
if result in words.words(): | |
row_1_valid_words.append(result) | |
unique_row_1 = set(row_1_valid_words) | |
print(f"{len(unique_row_1)} unique solutions found: {unique_row_1}") | |
# finds all the valid words for row 2 | |
print("checking row 2...") | |
row_2_valid_words = [] | |
for gold_character in gold_word: | |
for blue_character in blue_word: | |
for green_character in green_word: | |
row_2 = initial_state[2] | |
row_2[0] = blue_character | |
row_2[1] = gold_character | |
row_2[2] = green_character | |
result = ''.join(row_2) | |
if result in words.words(): | |
row_2_valid_words.append(result) | |
unique_row_2 = set(row_2_valid_words) | |
print(f"{len(unique_row_2)} solutions found: {unique_row_2}") | |
# finds a combination of valid words from the rows that creates a valid 9 letter word | |
print("finding valid 9 letter word(s)...") | |
solutions = [] | |
for row_0_valid in unique_row_0: | |
for row_1_valid in unique_row_1: | |
for row_2_valid in unique_row_2: | |
candidate = row_0_valid + row_1_valid + row_2_valid | |
if candidate in words.words(): | |
solutions.append(candidate) | |
return solutions | |
def engineering_problem(): | |
""" | |
Returns the word given a series of gears objects | |
Could probably make it so you can create a GearPuzzle class | |
which specifies an original direction and if any Gears are connected | |
by a chain to calculate rotation direction to make this scale to any size | |
puzzle but I'll leave that for another time | |
""" | |
gear_0 = Gear("RMNOPQ", -1) | |
gear_1 = Gear("EFGHIJKL", 1) | |
gear_2 = Gear("JBCDEFGHI", 1) | |
gear_3 = Gear("KLMNO", -1) | |
gear_4 = Gear("XYZTUVW", -1) | |
gears = [gear_0, gear_1, gear_2, gear_3, gear_4] | |
word = [] | |
for gear in gears: | |
word.append(gear.rotate(20)) | |
return ''.join(word) | |
def next_alpha(s, increment): | |
""" | |
Returns the letter in the alphabet that is x away from the given letter | |
""" | |
return chr((ord(s.upper()) + increment - 65) % 26 + 65) | |
def codebreaking_problem(): | |
""" | |
Brute forces the Caesar Shift | |
This doesn't actually WORK because NLTK doesn't recognise plurals | |
A bit of sanitisation could handle it or you could just read the final output | |
""" | |
# only handles the obvious what.3.words address | |
coded_string = "pvucpbse.hsje.sfkpjot" | |
# removes full stop to make checking if it's all words easier | |
coded_string_tokens = coded_string.split('.') | |
for i in range (1, 26): | |
result = [] | |
# increments all characters in string | |
for token in coded_string_tokens: | |
result.append(''.join([next_alpha(char, i) for char in token]).lower()) | |
# checks if all resultant words are valid english words | |
is_word = True | |
print(result) | |
for potential_word in result: | |
print(potential_word) | |
if potential_word not in words.words(): | |
is_word = False | |
else: | |
print(f"{potential_word} is a valid english word!") | |
# if they are, assume that this is the answer | |
if is_word: | |
return result | |
# would be interesting to see if there are any sentences that are | |
# Caesar Shift hash collisions | |
return ("No valid Caesar Shift found") | |
print(analysis_problem()) | |
print(coding_problem()) | |
print(engineering_problem()) | |
print(codebreaking_problem()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment