Python GUI 041 – Transpo – Route Main File

It’s been so long, I’ve forgotten what I’d initially set out to do.
Oh yeah, auto-solvers. This is the main file for the Route transposition functions.

Function list:
route_make_frames():. TKinker code for the E/D entry screen
route_ed(): ......... Encryption/Decryption code
get_box_dim(): ...... Make sure we have a complete rectangle
check_factors(): .... Make sure we have a complete rectangle
route_showtemplate(): Show the intermediary encryption steps
route_solve(): ...... The auto-solver function

This file contains the Route-specific functions for both manual encryption and decryption (E/D), and for the autosolver. In support of the manual encryption part, I also want to see the intermediary steps for on-by, and H/V flipping, which is contained in route_showtemplate(). Below are all of the related screen caps.


(Blank GUI start screen)


(Selecting a cipher type)


(Manual encryption/decryption entry screen)


(Manual encryption/decryption entry screen with encrypted message)


(Intermediate Route steps, for on-by and flip)


(Auto-solver screen for handling digital CONs)


(Suggested solutions screen)


(Digital CONs GUI with solved CON)

####################
# transpo_route.py #
####################

# Imports.

import tkinter as tk
from tkinter import Menu, ttk
from tkinter.messagebox import showinfo, askyesno, showerror
from crypt_utils import string_cleaner, count_grams, sort_first
from transpo_utils import ravel, unravel, get_factors, key_to_str, transpo_solver_data
from transpo_route_utils import *

"""
route_make_frames()

Nothing really special here. Effectively, I just repurposed the code from Crypto Solver.
"""

def route_make_frames(root, entries, frames_list, use_font, \
..................... fixed_font, ciphertype):

... frame = ttk.Frame(root)

... frame.columnconfigure(0, weight=1)
... frame.columnconfigure(1, weight=1)
... frame.columnconfigure(2, weight=1)

... label_parms = [('Title:', 0, 0),
.................. ('Length:', 0, 3), ('Box Width:', 0, 4), \

.................. ('Input:', 0, 5), ('Output:', 0, 6)
................. ]

... for label_name, label_col, label_row in label_parms:
....... ttk.Label(frame, text= label_name).grid(column=label_col, \

................. row=label_row, sticky=tk.W)

... entry_parms = [('title', 1, 0, 80), ('key', 1, 4, 20)]

... for param_name, param_col, param_row, param_width in entry_parms:
....... entry_text = tk.StringVar()
....... entry_widget = ttk.Entry(frame, width=param_width, \

.......................... textvariable=entry_text, font=use_font)
....... entry_widget.grid(column = param_col, row = param_row, \

......................... columnspan = 2, sticky=tk.W, padx=5, pady=5)
....... entries[param_name] = entry_text

... route_types = ['Rows', 'Cols', 'Alt Rows', 'Alt Cols', 'Diags', \
.................. 'Alt Diags', 'Rev Diags', 'Alt Rev Diags', \
.................. 'Spiral', 'CCW Spiral']
... flip_types = ['Normal', 'Horizontal', 'Vertical', 'Both']
... dir_types = ['Normal', 'Reversed']

... onby_combo = ttk.Combobox(frame, values = route_types)
... onby_combo.set('On by')
... onby_combo.grid(column = 1, row = 1, sticky=tk.W, padx=5, pady=5)
... entries['onby'] = onby_combo

... flip_combo = ttk.Combobox(frame, values = flip_types)
... flip_combo.set('Flip type')
... flip_combo.grid(column = 2, row = 1, sticky=tk.W, padx=5, pady=5)
... entries['flip'] = flip_combo

... offby_combo = ttk.Combobox(frame, values = route_types)
... offby_combo.set('Off by')
... offby_combo.grid(column = 1, row = 2, sticky=tk.W, padx=5, pady=5)
... entries['offby'] = offby_combo

... dir_combo = ttk.Combobox(frame, values = dir_types)
... dir_combo.set('Text Direction')
... dir_combo.grid(column = 2, row = 2, sticky=tk.W, padx=5, pady=5)
... entries['route_dir'] = dir_combo

... button_check = ttk.Button(frame, text='Check', \
........command=lambda: check_factors(entries, '', 0)).grid( \
....... column=1, row=3, sticky=tk.W, padx=5, pady=5)

... in_area = tk.Text(frame, width=80, height=10)
... in_area.grid(column=1, row=5, columnspan = 2, sticky=tk.EW, \

................ padx=5, pady=5)
... in_area.configure(font= fixed_font)
... entries['in_area'] = in_area

... in_bar = ttk.Scrollbar(frame, orient = tk.VERTICAL)
... in_bar.grid(column=3, row=5, sticky=tk.NS)

... in_area['yscrollcommand'] = in_bar.set
... in_bar['command'] = in_area.yview

... out_area = tk.Text(frame, width=80, height=10)
... out_area.grid(column=1, row=6, columnspan = 2, sticky=tk.EW, \

................. padx=5, pady=5)
... out_area.configure(font= fixed_font)
... entries['out_area'] = out_area

... out_bar = ttk.Scrollbar(frame, orient = tk.VERTICAL)
... out_bar.grid(column=3, row=6, sticky=tk.NS)

... out_area['yscrollcommand'] = out_bar.set
... out_bar['command'] = out_area.yview

... frames_list['cipher_frame'] = frame

... entries['type'] = ciphertype
... frames_list['input_frame'] = frame

... return entries, frames_list, frame

"""
route_ed()

I hinted a bit about this section before. This is where the user can manually encrypt and decrypt Route Transposition ciphers, using the above data entry screen. The functions for handling the routes are in transpo_route_utils.py.
The basic order will be:

Write message into box with on_by
Perform flips, if any
Read message from box with off_by
Reverse direction of message string

"""

def route_ed(entries, action, msgstr, boxstr, onby, offby, flip, \
............ direction):

# Make sure user has selected all of the options.

... if onby == 'On by' or offby == 'Off by' or flip == 'Flip type' \
....... or direction == 'Text Direction':
....... showinfo('Bad Selection', 'Onby/Offby/Flip/Direction not set.')
....... return ''

# Get the user's options from the GUI fields, and convert
# them to our dict "enums".

... onby_route = onby_dict[onby.upper().replace(' ', '')]
... offby_route = offby_dict[offby.upper().replace(' ', '')]
... flip_option = flip_dict[flip.upper()]
... msg_direction = dir_dict[direction.upper()]

# Ensure user entered good rectangle dimensions.

... if len(boxstr.strip()) == 0:
....... showinfo('Bad Box Dimensions', 'Box width empty')
....... return ''

... if not boxstr.isnumeric():
....... showinfo('Bad Box Dimensions', 'Box width non-integer: %s' % (boxstr))
....... return ''

# Everything seems ok. Do more initialization.
# If width is a factor of the message length, use it.

... msglen = len(msgstr)
... box_factors = check_factors(entries, msgstr, 1)
... box_w, box_h = get_box_dim(box_factors, int(boxstr))

# User entered a bad width. Return.

... if box_w == -1:
....... showinfo('Bad Box Dimensions', \

.................'Box width (%s) not a factor of msg len (%s)' \
................ % (boxstr, msglen))
....... return ''

# Ok, we're good. Create box1 with position numbers and on_by.
# Create box2 using box1 and flip.

... box1 = make_box(msglen, box_w, box_h, onby_route)
... box2 = mirror_box(box1, box_w, box_h, flip_option)

# Read out encrypted position numbers using off_by.

... template_off = read_off_box(box2, box_w, box_h, offby_route)

# If action=0, encrypt the actual message using ravel().
# Otherwise, decrypt the actual message using unravel().
# We'll also reverse the message direction while we're at it.

... if action == 0:
....... str_out = ravel(string_direction(msgstr, msg_direction), \

....................... template_off)
... else:
....... str_out = unravel(msgstr, template_off)
....... str_out = string_direction(str_out, msg_direction)

# We're finished. Return the processed message.

... return str_out

"""
get_box_dim()

Read the factors of the message length, and make sure that the user's choice of rectangle width is valid. Return the width and height of the rectangle for the encryption/decryption steps.
"""

def get_box_dim(factors, width):
... for fpair in factors:
....... if fpair[0] == width:
........... return fpair[0], fpair[1]
....... if fpair[1] == width:
........... return fpair[1], fpair[0]
... return -1, -1

"""
check_factors()

User pressed the Check button. Test whether the message entered manually allows for a complete rectangle.
"""

def check_factors(entries, msg_in, action):

# For encryption, clean up the message using the alphabet of the
# selected language, and return the legal factors.

... if action == 0:
....... msglen = len(string_cleaner.clean(entries['in_area'] \

................... .get(1.0, 'end')))
....... fl = get_factors(msglen)

....... msg_out = 'Len: %s, Factors: ' % (msglen)
....... for elem in fl:
........... msg_out += '%s/%s ' % (elem[0], elem[1])

....... entries['message'].set(msg_out)

# For decryption, the message is already cleaned. Just return the
# legal factors.

... else:
....... msglen = len(msg_in)
....... return get_factors(msglen)

... return ''

"""
route_showtemplate()

This section is pretty much the same as route_ed() above, except that we're showing the message, we're only doing encryption, and we only want to show the intermediary steps of the on_by rectangle and any H/V flips.
"""

def route_showtemplate(entries, msgstr, boxstr, onby, offby, flip, \
...................... direction):

# Initialization.

... onby_route = onby_dict[onby.upper().replace(' ', '')]
... offby_route = offby_dict[offby.upper().replace(' ', '')]
... flip_option = flip_dict[flip.upper()]
... msg_direction = dir_dict[direction.upper()]

# Get rectangle dimensions.

... msglen = len(msgstr)
... box_factors = check_factors(entries, msgstr, 1)
... box_w, box_h = get_box_dim(box_factors, int(boxstr))

# Error check.

... if box_w == -1:
....... showinfo('Bad Box Dimensions', \

................ 'Box width (%s) not a factor of msg len (%s)' \
................ % (boxstr, msglen))
....... return ''

# Make the on_by rectangle and the flip rectangle.

... box1 = make_box(msglen, box_w, box_h, onby_route)
... box2 = mirror_box(box1, box_w, box_h, flip_option)

# Encrypt the message using the encrypted number positions.

... str1 = ravel(string_direction(msgstr, msg_direction), box1)
... str2 = ravel(string_direction(msgstr, msg_direction), box2)

... ret = ['%s %s' % (onby, flip)]
... for i in range(0, msglen, box_w):
....... ret.append('%s %s' % (str1[i:i + box_w], str2[i:i + box_w]))

... ret.append('\nOn by: %s, Off by: %s, Mirroring: %s, Text Direction: %s' %
.............. (onby, offby, flip, direction))

# Return the boxes as a formatted string for display.

... return '\n'.join(ret)

"""
route_solve()

Here's our auto-solver. Just run through all of the route, flip and direction enums, decrypt the message using the current encrypted positions, and do an n-gram count to check for "correctness." Return the solutions with the highest n-gram counts for display in the GUI. Note that this entire process takes less than a tenth of a second, so there's no need for time slicing.
"""

def route_solve():
... global transpo_solver_data

# The CON data is in the transpo_solver_data object.
# Read the most-used fields to variables for speed.

... msg_master, key, hint, attack_type, option, box_width, \
....... width_max, cnt_settings = transpo_solver_data.get()

# Error checking.

... if box_width == 0:
....... showinfo('Bad Selection', 'Min. Width not set.')
....... return

# Get the message length and do more error checking.

... msglen = len(msg_master)
... if msglen % box_width != 0:
....... showinfo('Bad Selection', 'Min. Width not a factor of %s.' \

................ % msglen)
....... return

# We're good. Calculate the rectangle height.
# And do more initialization.

... box_height = int(msglen / box_width)
... ret = []
... strout = ''

# Get the current highest n-gram count, and the offset for m-lesser
# counts for inclusion in the list of solutions.

... cnt_max, offset = cnt_settings[0], cnt_settings[1]

# Start running through all of the route combinations.

... for direct in dir_dict:
....... msg = string_direction(msg_master, dir_dict[direct])

....... for on_dir in onby_dict:
........... box1 = make_box(msglen, box_width, box_height, \

........................... onby_dict[on_dir])
........... for flip_type in flip_dict:
............... box2 = mirror_box(box1, box_width, box_height, \

................................. flip_dict[flip_type])

............... for off_dir in offby_dict:
................... msgout = read_off_box(box2, box_width, \

.................................box_height, offby_dict[off_dir])

................... for dir2 in dir_dict:
....................... msgout = string_direction(msgout, \

................................................. dir_dict[dir2])

# We've fully scrambled the route positions.
# Decrypt the cipher with these positions using unravel().

....................... strout = unravel(msg_master, msgout)

# Do the n-gram count.

....................... cnt = count_grams(strout)

# If we have a crib or hint, check if it's contained
# in our decrypted message. If so, increment the count by 50.

....................... if len(hint) > 0 and hint in strout:
........................... cnt += 50

# If the n-gram count is greater than max count - offset,
# up max count if necessary, and add the solution to our
# output list.

....................... if cnt > cnt_max - offset:
........................... if cnt > cnt_max:
............................... cnt_max = cnt

........................... ret.append([cnt, '%s, %s' % \
...................................... (on_dir.lower(), \
....................................... off_dir.lower()), strout])

# Sort the solutions by descending count.
# Save the output list to the solutions member of our
# data object and return to the GUI.

... ret.sort(key=sort_first, reverse=True)
... transpo_solver_data.solutions = ret

... return

Next up: No idea

Published by The Chief

Who wants to know?

Leave a comment

Design a site like this with WordPress.com
Get started