Skip to content

Instantly share code, notes, and snippets.

@quotepilgrim
Last active December 29, 2022 00:38
Show Gist options
  • Select an option

  • Save quotepilgrim/91152bea4d5c744ae911ca6364e9d526 to your computer and use it in GitHub Desktop.

Select an option

Save quotepilgrim/91152bea4d5c744ae911ca6364e9d526 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""Northeast Southwest Cipher
This is an implementation of a manual cipher I have devised, which is similar
to the Vigenère and Playfair ciphers.
The enciphering method goes as follows:
Start by arranging the alphabet in a 5x5 square, with one letter being omitted;
each instance of that letter in the message will be replaced by another chosen
letter. By default the omitted letter is "j" and its replacement is "i"; this
can be changed with the -r argument. A keyword may also be used to reorder the
letters of the alphabet; it can be specified with the -k argument.
Then, for every letter in the plain text, find it in the square and write down
an adjacent letter in one of the four cardinal directions. After each letter,
change direction by going around a compass rose clockwise or widdershins.
By default we start from north and rotate clockwise; the starting direction
can be specificied with the -d argument and rotation can be changed with -w.
The process of deciphering a message is the same as enciphering, only starting
from the opposite direction (north<->south, east<->west); everything else needs
to be the same as what was used for enciphering.
"""
import argparse
import os
parser = argparse.ArgumentParser()
parser.add_argument("message", metavar="MESSAGE",
help="a text file containing the message to be enciphered")
parser.add_argument("-k", dest="keyword",
help="a keyword used for generating an alphabet key")
parser.add_argument("-r", dest="replacement", metavar="REPLACEMENT",
help='a pair of leters, where the first one is omitted'
' from the alphabet key ("j" by default) and every instance'
' of that letter in the message is replaced with the second'
' letter from the pair ("i" by default)')
parser.add_argument("-d", dest="direction", metavar="DIRECTION",
choices=["n","e","s","w","north","east","south","west"],
help='the starting direction for enciphering the message;'
' can be any of "north", "east", "south", "west" or the'
' first letter of one of these options')
parser.add_argument("-w", dest="widdershins", action='store_true',
help="encipher the message counterclockwisely")
parser.add_argument("-o", dest="output",
help="output message to specified text file")
args = parser.parse_args()
alphabet = "abcdefghijklmnopqrstuvwxyz"
delchar, subchar = "j", "i"
def make_key(k):
# Generate alphabet key from keyword
key_base = k.replace(delchar, subchar) + alphabet.replace(delchar, "")
key = ""
for c in key_base:
if c not in key:
if c in alphabet:
key += c
return key
def replace_check(r):
# Check if replacement value is exactly two letters from alphabet
if len(r) != 2:
return 1
if r[0] == r[1]:
return 1
for c in r.lower():
if c not in alphabet:
return 1
return
if args.replacement:
if replace_check(args.replacement):
parser.error("replacement must consist of a pair of different letters")
delchar = args.replacement[0].lower()
subchar = args.replacement[1].lower()
if args.keyword:
key=make_key(args.keyword.lower())
else:
key=alphabet.replace(delchar, "")
if os.path.isfile(args.message):
with open(args.message, "r") as file:
message = file.read()
else:
parser.error('"' + args.message + '" does not exist or is not a file')
direction = "nesw".index(args.direction[0]) if args.direction else 0
index = 0
rotation = 3 if args.widdershins else 1
output = ""
# Loop through characters in the input message
for c in message:
# Checks use lowercase letters
l = c.lower()
if l == delchar:
l = subchar
if l in key:
for i in key:
if l == i:
# Find the letter's index in the alphabet key
n = key.index(i)
# Encipher letter according to current direction,
# treating alphabet as being on a 5x5 grid and
# wrapping around edges as necessary.
if direction == 0: # North
n -= 5
elif direction == 1: # East
# Right edge wrap
if n % 5 == 4:
n -= 4
else:
n += 1
elif direction == 2: # South
n += 5
elif direction == 3: # West
# Left edge wrap
if n % 5 == 0:
n += 4
else:
n -= 1
# Top edge wrap
if n <= 0:
n += 25
# Bottom edge wrap
n = n % 25
# Write enciphered letter to output
if str.isupper(c):
output += key[n].upper()
else:
output += key[n]
direction = (direction + rotation) % 4
else:
output += c
if args.output:
if os.path.exists(args.output):
parser.error('"' + args.output + '" already exists')
else:
with open(args.output, "w") as file:
file.write(output)
else:
print(output, end="")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment