import string
import re
PAD = 3
COLUMNS_PER_ROW = 15
# ----------------------
# ALPHABET & CLEANING
# ----------------------
def build_alphabet():
return list(string.ascii_lowercase)
def clean_message(message):
return re.sub(r'[^a-z]', '', message.lower())
# ----------------------
# ENCODER
# ----------------------
def encode_message(message, key):
alphabet = build_alphabet()
key = key.lower()
message = clean_message(message)
sequence, numbers, directions = [], [], []
key_len = len(key)
for i, letter in enumerate(message):
col = i % key_len
base_letter = key[col]
base_index = alphabet.index(base_letter)
letter_index = alphabet.index(letter)
diff = (letter_index - base_index) % 26
if diff == 0:
steps, direction = 0, "A"
elif diff <= 13:
steps, direction = diff, "V"
else:
steps, direction = 26 - diff, "A"
sequence.append(str(col + 1))
numbers.append(str(steps))
directions.append(direction)
return sequence, numbers, directions
# ----------------------
# DECODER
# ----------------------
def decode_message(sequence, numbers, directions, key):
alphabet = build_alphabet()
key = key.lower()
decoded = []
key_len = len(key)
for seq, num, d in zip(sequence, numbers, directions):
col = int(seq.strip()) - 1
base_letter = key[col % key_len]
base_index = alphabet.index(base_letter)
num = int(num.strip())
if num == 0:
letter = base_letter
elif d.upper() == "V":
letter = alphabet[(base_index + num) % 26]
else: # "A"
letter = alphabet[(base_index - num) % 26]
decoded.append(letter)
return decoded
# ----------------------
# PRETTY PRINT ENCODER CHART
# ----------------------
def print_encoder_chart(sequence, numbers, directions, decoded=None, wrap=COLUMNS_PER_ROW):
arrow_map = {"A": "^", "V": "v"}
decoded = decoded if decoded is not None else ['']*len(sequence)
for i in range(0, len(sequence), wrap):
seq_chunk = sequence[i:i+wrap]
num_chunk = numbers[i:i+wrap]
dir_chunk = directions[i:i+wrap]
dec_chunk = decoded[i:i+wrap]
# Vertical chart: Sequence / Steps / Arrows / Decoded
rows = []
for s, n, d, c in zip(seq_chunk, num_chunk, dir_chunk, dec_chunk):
arrow = arrow_map.get(d.upper(), "?")
rows.append([s, n, arrow, c])
for line_idx in range(4):
print("".join(f"{rows[col_idx][line_idx]:>{PAD}}" for col_idx in range(len(rows))))
print()
# ----------------------
# MULTI-LINE INPUT
# ----------------------
def read_multiline_input(prompt="Enter text (end with blank line):"):
print(prompt)
lines = []
while True:
try:
line = input()
except EOFError:
break
if line.strip() == "":
break
lines.append(line)
return "\n".join(lines)
# ----------------------
# UNIVERSAL DECODER INPUT PARSER
# ----------------------
def parse_pretty_input():
"""
Flexible parser: horizontal charts, multi-line, unlimited length.
Ignores arrows if present.
"""
print("Paste the encoded text block (end with blank line):")
all_lines = []
while True:
try:
line = input()
except EOFError:
break
if line.strip() == "" and all_lines:
break
elif line.strip() == "":
continue
all_lines.append(line.rstrip("\n"))
if not all_lines:
print("❌ No input detected. Please try again.")
return parse_pretty_input()
# Clean lines
all_lines = [l for l in all_lines if l.strip()]
sequence, numbers, directions = [], [], []
# Assume horizontal chart: groups of 3 lines (seq, num, dir)
for i in range(0, len(all_lines), 3):
seq_line = all_lines[i] if i < len(all_lines) else ""
num_line = all_lines[i+1] if i+1 < len(all_lines) else ""
dir_line = all_lines[i+2] if i+2 < len(all_lines) else ""
sequence.extend([x.strip() for x in seq_line.split() if x.strip()])
numbers.extend([x.strip() for x in num_line.split() if x.strip()])
directions.extend([x.strip() for x in dir_line.split() if x.strip()])
return sequence, numbers, directions
# ----------------------
# PRINT DECODED TEXT FLEXIBLE
# ----------------------
def print_decoded_text(decoded, wrap=None):
text = "".join(decoded)
if wrap is None:
print(text)
else:
for i in range(0, len(text), wrap):
print(text[i:i+wrap])
# ----------------------
# MAIN
# ----------------------
if __name__ == "__main__":
mode = input("Choose mode (encode/decode): ").strip().lower()
key = input("Enter key (any length): ").strip()
if mode == "encode":
plaintext = read_multiline_input()
seq, nums, dirs = encode_message(plaintext, key)
show_chart = input("Show chart mode? (y/n): ").strip().lower() == "y"
if show_chart:
print("\nEncoder chart:\n")
print_encoder_chart(seq, nums, dirs)
elif mode == "decode":
seq, nums, dirs = parse_pretty_input()
decoded = decode_message(seq, nums, dirs, key)
wrap_input = input("Enter wrap length for decoded text (or press Enter for no wrap): ").strip()
wrap = int(wrap_input) if wrap_input.isdigit() else None
print("\nDecoded plaintext:\n")
print_decoded_text(decoded, wrap)
else:
print("Invalid mode. Please choose 'encode' or 'decode'.")