diff --git a/.gitignore b/.gitignore
index 5da3876..641e6ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,4 @@ data/oeis.txt
other/ec-*.png
other/ec-*.txt
other/list-onions.txt
-out/
\ No newline at end of file
+tmp/
\ No newline at end of file
diff --git a/FailedAttempts.py b/LP/FailedAttempts.py
similarity index 100%
rename from FailedAttempts.py
rename to LP/FailedAttempts.py
diff --git a/HeuristicLib.py b/LP/HeuristicLib.py
similarity index 63%
rename from HeuristicLib.py
rename to LP/HeuristicLib.py
index 63bb898..2b37440 100755
--- a/HeuristicLib.py
+++ b/LP/HeuristicLib.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
-import re
-from RuneText import RuneText
from NGrams import NGrams
+from RuneText import RUNES
def normalized_probability(int_prob):
@@ -9,8 +8,6 @@ def normalized_probability(int_prob):
return [x / total for x in int_prob] # math.log(x / total, 10)
-RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ'
-re_norune = re.compile('[^' + RUNES + ']')
PROB_INT = [0] * 29
for k, v in NGrams.load(1, '').items(): # '-no-e', '-solved'
PROB_INT[RUNES.index(k)] = v
@@ -53,24 +50,3 @@ class Probability(object):
val = sum(abs(Probability(nums[x::keylen]).IC() - target_ioc)
for x in range(keylen))
return 1 - (val / keylen)
-
-
-#########################################
-# load page and convert to indices for faster access
-#########################################
-
-def load_indices(fname, interrupt, maxinterrupt=None, minlen=None, limit=None):
- with open(fname, 'r') as f:
- data = RuneText(re_norune.sub('', f.read())).index[:limit]
- if maxinterrupt is not None:
- # incl. everything up to but not including next interrupt
- # e.g., maxinterrupt = 0 will return text until first interrupt
- for i, x in enumerate(data):
- if x != interrupt:
- continue
- if maxinterrupt == 0:
- if minlen and i < minlen:
- continue
- return data[:i]
- maxinterrupt -= 1
- return data
diff --git a/HeuristicSearch.py b/LP/HeuristicSearch.py
similarity index 98%
rename from HeuristicSearch.py
rename to LP/HeuristicSearch.py
index b6f0f48..2eec534 100755
--- a/HeuristicSearch.py
+++ b/LP/HeuristicSearch.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import itertools # product, compress, combinations
import bisect # bisect_left, insort
-import lib as LIB
+from lib import affine_decrypt
#########################################
@@ -45,7 +45,7 @@ class GuessAffine(object):
best = 9999999
for s in range(29):
for t in range(29):
- shifted = [LIB.affine_decrypt(x, (s, t))
+ shifted = [affine_decrypt(x, (s, t))
for x in self.nums[offset::keylength]]
score = score_fn(shifted)
if score < best:
diff --git a/InterruptDB.py b/LP/InterruptDB.py
similarity index 87%
rename from InterruptDB.py
rename to LP/InterruptDB.py
index ad8b6a3..49fafce 100755
--- a/InterruptDB.py
+++ b/LP/InterruptDB.py
@@ -1,15 +1,9 @@
#!/usr/bin/env python3
import os
from HeuristicSearch import SearchInterrupt
-from HeuristicLib import load_indices, Probability
-
-RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ' # used in InterruptToWeb
-FILES_SOLVED = ['0_warning', '0_welcome', '0_wisdom', '0_koan_1',
- '0_loss_of_divinity', 'jpg107-167', 'jpg229',
- 'p56_an_end', 'p57_parable']
-FILES_UNSOLVED = ['p0-2', 'p3-7', 'p8-14', 'p15-22', 'p23-26',
- 'p27-32', 'p33-39', 'p40-53', 'p54-55']
-FILES_ALL = FILES_UNSOLVED + FILES_SOLVED
+from HeuristicLib import Probability
+from RuneText import RUNES, load_indices
+from LPath import FILES_ALL, FILES_UNSOLVED, LPath
#########################################
@@ -85,10 +79,10 @@ class InterruptDB(object):
@staticmethod
def load(dbname):
- if not os.path.isfile(f'InterruptDB/{dbname}.txt'):
+ if not os.path.isfile(LPath.InterruptDB(dbname)):
return {}
ret = {}
- with open(f'InterruptDB/{dbname}.txt', 'r') as f:
+ with open(LPath.InterruptDB(dbname), 'r') as f:
for line in f.readlines():
if line.startswith('#'):
continue
@@ -104,7 +98,7 @@ class InterruptDB(object):
@staticmethod
def write(name, score, irp, irpmax, keylen, nums, dbname='db_main'):
- with open(f'InterruptDB/{dbname}.txt', 'a') as f:
+ with open(LPath.InterruptDB(dbname), 'a') as f:
nums = ','.join(map(str, nums))
f.write(f'{name}|{irpmax}|{score:.5f}|{irp}|{keylen}|{nums}\n')
@@ -134,7 +128,7 @@ class InterruptIndices(object):
@staticmethod
def write(dbname='db_indices'):
- with open(f'InterruptDB/{dbname}.txt', 'w') as f:
+ with open(LPath.InterruptDB(dbname), 'w') as f:
f.write('# file | total runes in file | interrupt | indices\n')
for name in FILES_ALL:
fname = f'pages/{name}.txt'
@@ -149,7 +143,7 @@ class InterruptIndices(object):
@staticmethod
def read(dbname='db_indices'):
- with open(f'InterruptDB/{dbname}.txt', 'r') as f:
+ with open(LPath.InterruptDB(dbname), 'r') as f:
ret = {}
for line in f.readlines():
if line.startswith('#'):
@@ -169,8 +163,9 @@ class InterruptIndices(object):
#########################################
class InterruptToWeb(object):
- def __init__(self, dbname, template='InterruptDB/template.html'):
- self.template = template
+ def __init__(self, dbname, template='template.html'):
+ with open(LPath.results(template), 'r') as f:
+ self.template = f.read()
self.indices = InterruptIndices()
self.scores = {}
db = InterruptDB.load(dbname)
@@ -252,8 +247,6 @@ class InterruptToWeb(object):
return f'
{trh}{"".join(trd[1:])}{trbest}
'
def make(self, outfile, pmin=1.25, pmax=1.65):
- with open(self.template, 'r') as f:
- html = f.read()
nav = ''
txt = ''
for i in range(29):
@@ -263,10 +256,10 @@ class InterruptToWeb(object):
nav += f'{RUNES[i]}\n'
txt += f'Interrupt {i}: {RUNES[i]}
'
txt += self.table_interrupt(i, pmin, pmax)
- html = html.replace('__NAVIGATION__', nav)
+ html = self.template.replace('__NAVIGATION__', nav)
html = html.replace('__TAB_RELIABLE__', self.table_reliable())
html = html.replace('__INTERRUPT_TABLES__', txt)
- with open(outfile, 'w') as f:
+ with open(LPath.results(outfile), 'w') as f:
f.write(html)
@@ -279,12 +272,10 @@ def create_initial_db(dbname, minkl=1, maxkl=32, max_irp=20, irpset=range(29)):
oldValues = {k: set((a, b, c) for a, _, b, c, _ in v)
for k, v in oldDB.items()}
for irp in irpset: # interrupt rune index
- # for name in FILES_UNSOLVED:
for name in FILES_ALL:
- fname = f'pages/{name}.txt'
- data = load_indices(fname, irp, maxinterrupt=max_irp)
+ data = load_indices(LPath.page(name), irp, maxinterrupt=max_irp)
db = InterruptDB(data, irp)
- print('load:', fname, 'interrupt:', irp, 'count:', db.irp_count)
+ print('load:', name, 'interrupt:', irp, 'count:', db.irp_count)
for keylen in range(minkl, maxkl + 1): # key length
if (db.irp_count, irp, keylen) in oldValues.get(name, []):
print(f'{keylen}: skipped.')
@@ -305,9 +296,8 @@ def find_secondary_solutions(db_in, db_out, threshold=0.75, max_irp=20):
search_set.add((name, irp, kl))
print('searching through', len(search_set), 'files.')
for name, irp, kl in search_set:
- fname = f'pages/{name}.txt'
- print('load:', fname, 'interrupt:', irp, 'keylen:', kl)
- data = load_indices(fname, irp, maxinterrupt=max_irp)
+ print('load:', name, 'interrupt:', irp, 'keylen:', kl)
+ data = load_indices(LPath.page(name), irp, maxinterrupt=max_irp)
db = InterruptDB(data, irp)
c = db.make_secondary(db_out, name, kl, threshold)
print('found', c, 'additional solutions')
@@ -317,6 +307,5 @@ if __name__ == '__main__':
# find_secondary_solutions('db_high', 'db_high_secondary', threshold=1.4)
# find_secondary_solutions('db_norm', 'db_norm_secondary', threshold=0.55)
# create_initial_db('db_norm', minkl=1, maxkl=32, max_irp=20)
- # InterruptToWeb('db_high').make('InterruptDB/index_high.html')
- InterruptToWeb('db_norm').make(
- 'InterruptDB/index_norm.html', pmin=0.40, pmax=0.98)
+ # InterruptToWeb('db_high').make('index_high.html')
+ InterruptToWeb('db_norm').make('index_norm.html', pmin=0.40, pmax=0.98)
diff --git a/LP/LPath.py b/LP/LPath.py
new file mode 100755
index 0000000..20f66ad
--- /dev/null
+++ b/LP/LPath.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+import os.path
+
+FILES_SOLVED = ['0_warning', '0_welcome', '0_wisdom', '0_koan_1',
+ '0_loss_of_divinity', 'jpg107-167', 'jpg229',
+ 'p56_an_end', 'p57_parable']
+FILES_UNSOLVED = ['p0-2', 'p3-7', 'p8-14', 'p15-22', 'p23-26',
+ 'p27-32', 'p33-39', 'p40-53', 'p54-55']
+FILES_ALL = FILES_UNSOLVED + FILES_SOLVED
+
+LP_MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
+LP_ROOT_DIR = os.path.relpath(os.path.dirname(LP_MODULE_DIR))
+
+
+class LPath(object):
+ @staticmethod
+ def root(fname):
+ return os.path.join(LP_ROOT_DIR, fname)
+
+ @staticmethod
+ def page(fname):
+ return os.path.join(LP_ROOT_DIR, 'pages', fname + '.txt')
+
+ @staticmethod
+ def data(fname, ext='txt'):
+ return os.path.join(LP_ROOT_DIR, 'data', f'{fname}.{ext}')
+
+ @staticmethod
+ def tmp(fname, ext='txt'):
+ return os.path.join(LP_ROOT_DIR, 'tmp', f'{fname}.{ext}')
+
+ @staticmethod
+ def InterruptDB(fname):
+ return os.path.join(LP_ROOT_DIR, 'InterruptDB', fname + '.txt')
+
+ @staticmethod
+ def results(fname):
+ return os.path.join(LP_ROOT_DIR, 'InterruptDB', fname)
diff --git a/NGrams.py b/LP/NGrams.py
similarity index 60%
rename from NGrams.py
rename to LP/NGrams.py
index a20677f..0a2dd81 100755
--- a/NGrams.py
+++ b/LP/NGrams.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import re
-from RuneText import alphabet, RuneText
+from RuneText import RUNES, re_norune, RuneText
+from LPath import LPath
#########################################
@@ -28,11 +29,10 @@ class NGrams(object):
@staticmethod
def make(gramsize, infile, outfile):
- allowed_chr = [x[1] for x in alphabet]
with open(infile, 'r') as f:
- data = re.sub('[^{}]'.format(''.join(allowed_chr)), '', f.read())
+ data = re_norune.sub('', f.read())
- res = {x: 0 for x in allowed_chr} if gramsize == 1 else {}
+ res = {x: 0 for x in RUNES} if gramsize == 1 else {}
for i in range(len(data) - gramsize + 1):
ngram = data[i:i + gramsize]
try:
@@ -47,19 +47,28 @@ class NGrams(object):
@staticmethod
def load(ngram=1, prefix=''):
ret = {}
- with open(f'data/p{prefix}-{ngram}gram.txt', 'r') as f:
+ with open(LPath.data(f'p{prefix}-{ngram}gram'), 'r') as f:
for line in f.readlines():
r, v = line.split()
ret[r] = int(v)
return ret
-# NGrams.translate('data/baseline-text.txt', 'data/baseline-rune.txt', False)
-# for i in range(1, 6):
-# print(f'generate {i}-gram file')
-# NGrams.make(i, infile='data/baseline-rune-words.txt',
-# outfile=f'data/p-{i}gram.txt')
-# NGrams.make(i, infile='_solved.txt',
-# outfile=f'data/p-solved-{i}gram.txt')
-# NGrams.make(i, infile='data/baseline-rune-no-e.txt',
-# outfile=f'data/p-no-e-{i}gram.txt')
+def make_translation(stream=False): # if true, ignore spaces / word bounds
+ NGrams.translate(LPath.data('baseline-text'),
+ LPath.data('baseline-rune'), stream)
+
+
+def make_ngrams(max_ngram=1):
+ for i in range(1, max_ngram + 1):
+ print(f'generate {i}-gram file')
+ NGrams.make(i, infile=LPath.data('baseline-rune-words'),
+ outfile=LPath.data(f'p-{i}gram'))
+ NGrams.make(i, infile=LPath.root('_solved.txt'),
+ outfile=LPath.data(f'p-solved-{i}gram'))
+ NGrams.make(i, infile=LPath.data('baseline-rune-no-e'),
+ outfile=LPath.data(f'p-no-e-{i}gram'))
+
+
+# make_translation(stream=False)
+# make_ngrams(5)
diff --git a/RuneRunner.py b/LP/RuneRunner.py
similarity index 97%
rename from RuneRunner.py
rename to LP/RuneRunner.py
index c004c71..8a08e89 100755
--- a/RuneRunner.py
+++ b/LP/RuneRunner.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
-from RuneText import RuneText
-import lib as LIB
import sys
+from RuneText import RuneText
+import lib as utils
#########################################
@@ -86,11 +86,11 @@ class RuneReader(object):
self.loaded_file = None
self.words = {x: [] for x in range(20)} # increase for longer words
- def load(self, data=None, file=None):
+ def load(self, data=None, file=None, limit=None):
self.loaded_file = None
if not data:
with open(file, 'r') as f:
- data = f.read()
+ data = f.read()[:limit]
self.loaded_file = file
self.data = data if isinstance(data, RuneText) else RuneText(data)
self.generate_word_list()
@@ -219,7 +219,7 @@ class RuneRunner(object):
if self.output.VERBOSE:
self.output.write(n1=('' if is_first else '+') + str(num))
return
- prm = LIB.is_prime(num)
+ prm = utils.is_prime(num)
if typ == 'w': # word
tt = ('' if is_first else ' + ') + str(num) + ('*' if prm else '')
self.output.write(n2=tt)
@@ -229,7 +229,7 @@ class RuneRunner(object):
self.output.mark = False
# if not is_first:
sffx = ' = {}'.format(num) + ('*' if prm else '')
- if LIB.is_prime(LIB.rev(num)):
+ if utils.is_emirp(num):
sffx += '√'
if self.output.VERBOSE:
self.output.write(n2=sffx)
diff --git a/RuneSolver.py b/LP/RuneSolver.py
similarity index 98%
rename from RuneSolver.py
rename to LP/RuneSolver.py
index f2b6385..e0602d4 100755
--- a/RuneSolver.py
+++ b/LP/RuneSolver.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
from RuneRunner import RuneRunner
from RuneText import Rune, RuneText
-import lib as LIB
+from lib import affine_decrypt
#########################################
@@ -186,7 +186,7 @@ class VigenereSolver(RunningKeySolver):
class AffineSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index):
- return LIB.affine_decrypt(rune_index, self.KEY_DATA[key_index])
+ return affine_decrypt(rune_index, self.KEY_DATA[key_index])
#########################################
diff --git a/RuneText.py b/LP/RuneText.py
similarity index 83%
rename from RuneText.py
rename to LP/RuneText.py
index b16f5b5..98b8fbf 100755
--- a/RuneText.py
+++ b/LP/RuneText.py
@@ -1,4 +1,7 @@
#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+import re # load_indices
+
white_rune = {'•': ' ', '⁘': '.', '⁚': ',', '⁖': ';', '⁜': '#'}
white_text = {v: k for k, v in white_rune.items()}
alphabet = [ # Using last value for display. Custom added: V
@@ -14,8 +17,9 @@ alphabet = [ # Using last value for display. Custom added: V
]
text_map = {t: r for _, r, ta in alphabet for t in ta}
rune_map = {r: t for _, r, ta in alphabet for t in ta}
-index_map = [r for _, r, _ in alphabet] # array already sorted
primes_map = {r: p for p, r, _ in alphabet}
+RUNES = [r for _, r, _ in alphabet] # array already sorted
+re_norune = re.compile('[^' + ''.join(RUNES) + ']')
# del alphabet # used in playground for GP display
@@ -37,7 +41,7 @@ class Rune(object):
@property
def rune(self):
if self._rune is None:
- self._rune = index_map[self._index] if self._index < 29 else '•'
+ self._rune = RUNES[self._index] if self._index < 29 else '•'
return self._rune
@property
@@ -54,7 +58,7 @@ class Rune(object):
def index(self):
if self._index is None:
r = self._rune
- self._index = index_map.index(r) if r in index_map else 29
+ self._index = RUNES.index(r) if r in RUNES else 29
return self._index
@property
@@ -182,37 +186,18 @@ class RuneText(object):
res.append(Rune(r=rune, t=char))
return res
- def as_dict(self):
- ret = {'r': '', 't': '', 'i': [], 'p': []}
- for x in self._data:
- ret['r'] += x.rune
- ret['t'] += x.text
- ret['i'].append(x.index)
- ret['p'].append(x.prime)
- return ret
-
def description(self, count=False, index=True, indexWhitespace=False):
- if len(self) == 0:
- return None
- fmt = '{0} ({1}) – {2} ({3})' if count else '{0} – {2}'
- d = self.as_dict()
- if index:
- fmt += ' – {4}'
- if not indexWhitespace:
- d['i'] = [x for x in d['i'] if x != 29]
- return fmt.format(d['r'], len(d['r']), d['t'], len(d['t']), d['i'])
+ return None if len(self) == 0 else \
+ self.rune + (f' ({len(self)})' if count else '') + ' - ' + \
+ self.text + (f' ({len(self.text)})' if count else '') + \
+ (f' - {self.index if indexWhitespace else self.index_rune_only}'
+ if index else '')
def zip_sub(self, other):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x - y for x, y in zip(self._data, other._data)])
- @property
- def rune_sum(self):
- if self._rune_sum is None:
- self._rune_sum = sum(x.prime for x in self._data)
- return self._rune_sum
-
@property
def text(self):
return ''.join(x.text for x in self._data)
@@ -226,9 +211,19 @@ class RuneText(object):
return [x.index for x in self._data if x.kind != 'l']
@property
- def index_no_whitespace(self):
+ def index_rune_only(self):
return [x.index for x in self._data if x.index != 29]
+ @property
+ def prime(self):
+ return [x.prime for x in self._data]
+
+ @property
+ def prime_sum(self):
+ if self._rune_sum is None:
+ self._rune_sum = sum(self.prime)
+ return self._rune_sum
+
def __getitem__(self, key):
if isinstance(key, str):
return [getattr(x, key) for x in self._data]
@@ -255,3 +250,24 @@ class RuneText(object):
def __repr__(self):
return f'RuneText<{len(self._data)}>'
+
+
+#########################################
+# load page and convert to indices for faster access
+#########################################
+
+def load_indices(fname, interrupt, maxinterrupt=None, minlen=None, limit=None):
+ with open(fname, 'r') as f:
+ data = RuneText(re_norune.sub('', f.read())).index_rune_only[:limit]
+ if maxinterrupt is not None:
+ # incl. everything up to but not including next interrupt
+ # e.g., maxinterrupt = 0 will return text until first interrupt
+ for i, x in enumerate(data):
+ if x != interrupt:
+ continue
+ if maxinterrupt == 0:
+ if minlen and i < minlen:
+ continue
+ return data[:i]
+ maxinterrupt -= 1
+ return data
diff --git a/LP/__init__.py b/LP/__init__.py
new file mode 100644
index 0000000..e1c85bc
--- /dev/null
+++ b/LP/__init__.py
@@ -0,0 +1,17 @@
+import sys
+if True:
+ sys.path.append(__path__[0])
+
+import lib as utils
+from LPath import FILES_ALL, FILES_UNSOLVED, FILES_SOLVED
+from LPath import LPath as path
+from RuneSolver import VigenereSolver, AffineSolver, AutokeySolver, SequenceSolver
+from RuneText import Rune, RuneText
+from RuneText import RUNES, alphabet, load_indices
+
+from HeuristicSearch import GuessVigenere, GuessAffine, GuessPattern
+from HeuristicSearch import SearchInterrupt
+from HeuristicLib import Probability
+from InterruptDB import InterruptDB
+
+from FailedAttempts import NGramShifter
diff --git a/lib.py b/LP/lib.py
similarity index 96%
rename from lib.py
rename to LP/lib.py
index 2131cea..3522074 100755
--- a/lib.py
+++ b/LP/lib.py
@@ -1,11 +1,7 @@
#!/usr/bin/env python3
import math
-AFFINE_INV = None
-
-# yes it will report 2,3,5 as non-prime
-# though why add a check if it will never be tested anyway
def is_prime(num):
if isinstance(num, str):
num = int(num)
@@ -30,6 +26,10 @@ def rev(num): # or int(str(num)[::-1])
return revs
+def is_emirp(num):
+ return is_prime(rev(num))
+
+
def power(x, y, p):
res = 1
x %= p
@@ -74,6 +74,9 @@ def elliptic_curve(x, a, b, r):
return y, -y % r
+AFFINE_INV = None
+
+
def affine_inverse(s, n=29):
def fn(s, n):
g = [n, s]
diff --git a/out/0_welcome.8.txt b/out/0_welcome.8.txt
deleted file mode 100644
index 63181eb..0000000
--- a/out/0_welcome.8.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-8, 1.9043, [10, 16, 10, 9, 10, 16, 26, 23], [1, 4, 5, 6, 7, 11, 12, 14, 19, 20, 21, 23, 25]
-LEOJGTHFE#
-WELMOME,PILGEIM,TO THE GEEAT JOURNOY TOWARD TE END OF ALC THNGS.
-IT IS DOT AN EASY URIP,BUT FOE THOSE WHO FAND THEIR WNY HERE IT AS A NECESFARY ONE.
-ACONG THE WAY JOU WILL FIDD AN END TB ALL STRULGLE AND SSMFMOS,TFUR ANNOCENCO,YOUR ILLSSIANS,YOUE CERTAINUY,AND YOUE REALITY.
-UCTIMATELJ,YOU WILL HISCOUER NN END TO SOLF.
-IT IS THRBUGH THIS PALGRIMAGO THAT WE SHNPE OURSECUES AND OSR REALITIOS.
-JOURNEJ DEEP WITHAN AND YOU NGILL ARRISE OUTSIDO.
-LICE THE IDSTAR,IT IF ONLY THROSGH GONG WITIN THAT WE RAY EMERGO#
-WIDSOM#
-YBU ARE A BEG UNTO YOUESELP.
-TFU AEE A LAW UNUO YOURSECF.
-EACH INTECLIGENCE AS HOLY.
-FOR NLL THAT LISES IS HOLJ#
-#
-AN INSTRSCTIAN#
-COFIEHA OPIAL DUH TAUF#
diff --git a/out/jpg107-167.13.txt b/out/jpg107-167.13.txt
deleted file mode 100644
index 88b4772..0000000
--- a/out/jpg107-167.13.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-13, 2.1477, [21, 10, 4, 0, 1, 19, 0, 18, 4, 18, 9, 0, 18], [2, 3]
-O COAN#
-DURNG A LESDON,THE MASTER EXNGLAINED THE I.
-"THE I ES THE UOICE OF THE CERCUMFERENCE,"HE DAID.
-WHEN ASCED AEY A STUDENT TO EOEPLAIN WHAT THAT IAEANT;THE MASTER SOID"IT IS A UOICE ENSIDE YOUR HEAD".
-"E DON'T HAUE A UOIPE IN MY HEAD," THOUGTT THE STUDENT,ANTH HE RAISED HIS HOND TO TELL THE MADTER.
-THE MASTER SAOPPED THE STUDEBT,AND SAID"THE UOECE THAT JUST SAITH YOU HAUE NO UOIPE IN YOUR HEAD;IS IE I.
-"AND THE STUDEBTS WERE ENLIGHAENED#
diff --git a/playground.py b/playground.py
index 2e50b44..de27176 100755
--- a/playground.py
+++ b/playground.py
@@ -1,12 +1,8 @@
#!/usr/bin/env python3
-from RuneSolver import VigenereSolver, AffineSolver, AutokeySolver
-from RuneText import alphabet, RuneText
-import lib as LIB
+import LP
-SOLVER = VigenereSolver()
-# SOLVER = AffineSolver()
-# SOLVER = AutokeySolver()
-SOLVER.input.load(file='_input.txt')
+SOLVER = LP.VigenereSolver() # VigenereSolver, AffineSolver, AutokeySolver
+SOLVER.input.load(file=LP.path.root('_input.txt'))
def main():
@@ -82,7 +78,7 @@ def command_a(cmd, args): # [a]ll variations
return False
if not args:
raise Warning('No input provided.')
- root = RuneText(args)
+ root = LP.RuneText(args)
inclIndex = 'q' not in cmd
if 'i' in cmd:
root = ~root
@@ -101,10 +97,10 @@ def command_d(cmd, args): # [d]ecrypt or single substitution
trythis = input('What is the encrypted text?: ').strip()
else:
trythis = args
- enc = RuneText(trythis)
+ enc = LP.RuneText(trythis)
print('encrypted:', enc.description())
target = input('What should the decrypted clear text be?: ').strip()
- plain = RuneText(target)
+ plain = LP.RuneText(target)
print('plaintext:', plain.description())
if len(enc) != len(plain):
print('Error: key length mismatch')
@@ -126,7 +122,7 @@ def command_f(cmd, args): # (f)ind word
search_term = None
s_len = int(args.strip()) # search words with n-length
except ValueError:
- search_term = RuneText(args)
+ search_term = LP.RuneText(args)
s_len = len(search_term)
cur_words = SOLVER.highlight_words_with_len(s_len)
@@ -165,14 +161,14 @@ def command_g(cmd, args): # (g)ematria primus
return False
def pp(i):
- p, r, t = alphabet[28 - i if rev else i]
+ p, r, t = LP.alphabet[28 - i if rev else i]
return '{:2d} {} {:3d} {}'.format(i, r, p, '/'.join(t))
rev = cmd[-1] in 'ir' or len(args) > 0 and args[0] in 'ir'
print('Gematria Primus (reversed)' if rev else 'Gematria Primus')
- half = (len(alphabet) >> 1) + (len(alphabet) & 1) # ceil
+ half = (len(LP.alphabet) >> 1) + (len(LP.alphabet) & 1) # ceil
for i in range(half):
- if i < len(alphabet) // 2:
+ if i < len(LP.alphabet) // 2:
print('{:22} {}'.format(pp(i), pp(i + half)))
else:
print(pp(i))
@@ -206,8 +202,8 @@ def command_h(cmd, args): # (h)ighlight
def command_k(cmd, args): # (k)ey manipulation
if cmd == 'k' or cmd == 'key':
- SOLVER.KEY_DATA = [x.index for x in RuneText(args)]
- print('set key: {}'.format(SOLVER.KEY_DATA))
+ SOLVER.KEY_DATA = LP.RuneText(args).index
+ print(f'set key: {SOLVER.KEY_DATA}')
elif cmd[1] == 's':
SOLVER.KEY_SHIFT = get_cmd_int(cmd, args, 'shift')
elif cmd[1] == 'r':
@@ -218,12 +214,12 @@ def command_k(cmd, args): # (k)ey manipulation
SOLVER.KEY_POST_PAD = get_cmd_int(cmd, args, 'post padding')
elif cmd[1] == 'i':
SOLVER.KEY_INVERT = not SOLVER.KEY_INVERT
- print('set key invert: {}'.format(SOLVER.KEY_INVERT))
+ print(f'set key invert: {SOLVER.KEY_INVERT}')
elif cmd == 'kj':
args = args.strip('[]')
pos = [int(x) for x in args.split(',')] if args else []
SOLVER.INTERRUPT_POS = pos
- print('set interrupt jumps: {}'.format(pos))
+ print(f'set interrupt jumps: {SOLVER.INTERRUPT_POS}')
else:
return False # command not found
SOLVER.run()
@@ -254,8 +250,8 @@ def command_p(cmd, args): # (p)rime test
if args and cmd not in 'prime':
return False
p = get_cmd_int(cmd, args, start=1)
- print(p, ':', LIB.is_prime(p))
- print(LIB.rev(p), ':', LIB.is_prime(LIB.rev(p)))
+ print(p, ':', LP.utils.is_prime(p))
+ print(LP.utils.rev(p), ':', LP.utils.is_emirp(p))
#########################################
@@ -265,17 +261,14 @@ def command_p(cmd, args): # (p)rime test
def command_t(cmd, args): # (t)ranslate
if cmd != 't':
return False
- word = RuneText(args)
- x = word.as_dict()
- psum = sum(x['p'])
- sffx = '*' if LIB.is_prime(psum) else ''
- if LIB.is_prime(LIB.rev(psum)):
- sffx += '√'
- print('runes({}): {}'.format(len(x['r']), x['r']))
- print('plain({}): {}'.format(len(x['t']), x['t']))
- print('reversed: {}'.format(''.join([x.rune for x in ~word])))
- print('indices: {}'.format(x['i']))
- print('prime({}{}): {}'.format(psum, sffx, x['p']))
+ word = LP.RuneText(args)
+ sffx = ''.join(['*' if LP.utils.is_prime(word.prime_sum) else '',
+ '√' if LP.utils.is_emirp(word.prime_sum) else ''])
+ print('runes({}): {}'.format(len(word), word.rune))
+ print('plain({}): {}'.format(len(word.text), word.text))
+ print('reversed: {}'.format((~word).rune))
+ print('indices: {}'.format(word.index))
+ print('prime({}{}): {}'.format(word.prime_sum, sffx, word.prime))
#########################################
@@ -286,7 +279,7 @@ def command_x(cmd, args): # e(x)ecute decryption
if cmd == 'x':
pass # just run the solver
elif cmd == 'xf': # reload from file
- file = f'pages/{args}.txt' if args else '_input.txt'
+ file = LP.path.page(args) if args else LP.path.root('_input.txt')
print('loading file:', file)
SOLVER.input.load(file=file)
args = None # so run() won't override data
diff --git a/probability.py b/probability.py
index c3ab0f6..b765d79 100755
--- a/probability.py
+++ b/probability.py
@@ -1,18 +1,11 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
-from RuneSolver import VigenereSolver, AffineSolver
-from HeuristicSearch import GuessVigenere, GuessAffine, GuessPattern
-from HeuristicSearch import SearchInterrupt
-from HeuristicLib import load_indices, Probability
-from InterruptDB import InterruptDB
-from RuneText import RuneText
-# from FailedAttempts import NGramShifter
+import LP
-RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ'
INVERT = False
KEY_MAX_SCORE = 0.05
AFF_MAX_SCORE = 0.04
-IRP_F_ONLY = False
+IRP_F_ONLY = True
session_files = []
@@ -22,9 +15,9 @@ session_files = []
def break_cipher(fname, candidates, solver, key_fn):
def fn_similarity(x):
- return Probability(x).similarity()
+ return LP.Probability(x).similarity()
- filename = f'pages/{fname}.txt'
+ filename = LP.path.page(fname)
slvr = solver()
slvr.input.load(file=filename)
slvr.output.QUIET = True
@@ -36,12 +29,12 @@ def break_cipher(fname, candidates, solver, key_fn):
for irp_count, score, irp, kl, skips in candidates:
if IRP_F_ONLY and irp != 0:
continue
- data = load_indices(filename, irp, maxinterrupt=irp_count)
+ data = LP.load_indices(filename, irp, maxinterrupt=irp_count)
if INVERT:
data = [28 - x for x in data]
- iguess = SearchInterrupt(data, (28 - irp) if INVERT else irp)
- print('score: {}, interrupt: {}, count: {}, solver: {}'.format(
- score, RUNES[irp], len(iguess.stops), key_fn.__name__))
+ iguess = LP.SearchInterrupt(data, (28 - irp) if INVERT else irp)
+ print('IoC: {}, interrupt: {}, count: {}, solver: {}'.format(
+ score, LP.RUNES[irp], len(iguess.stops), key_fn.__name__))
testcase = iguess.join(iguess.from_occurrence_index(skips))
key_score, key = key_fn(testcase).guess(kl, fn_similarity)
@@ -56,25 +49,25 @@ def break_cipher(fname, candidates, solver, key_fn):
while txtname in session_files:
txtname += '.'
session_files.append(txtname)
- outfile = f'out/{txtname}.txt'
+ outfile = LP.path.tmp(txtname)
with open(outfile, 'w') as f:
f.write(
f'{irp}, {kl}, {score:.4f}, {key_score:.4f}, {key}, {skips}\n')
slvr.output.file_output = outfile
- slvr.INTERRUPT = RUNES[irp]
+ slvr.INTERRUPT = LP.RUNES[irp]
slvr.INTERRUPT_POS = skips
slvr.KEY_DATA = key
slvr.run()
def pattern_solver(fname, irp=0):
- with open(f'pages/{fname}.txt', 'r') as f:
- orig = RuneText(f.read())
- # orig = RuneText('ᛄᚹᚻᛗᛋᚪ-ᛋᛁᚫᛇ-ᛋᛠᚾᛞ-ᛇᛞ-ᛞᚾᚣᚹᛗ.ᛞᛈ-ᛝᛚᚳᚾᛗᚾᚣ-ᛖᛝᛖᚦᚣᚢ-ᚱᚻᛁᚠ-ᛟᛝ-ᛚᛖᚫᛋᛚᚳᛋᛇ.ᚣᚾᚻᛄᚾᚳᛡ-ᚷᚳᛝ-ᛈᛝ-ᛡᚷᚦᚷᛖ.ᚻᛠᚣ-ᛄᛞᚹᛒ-ᛇᛄᛝᚩᛟ.ᛗᛠᚣᛋᛖᛚᚠ-ᚾᚫᛁ-ᛄᚹᚻᚻᛚᛈᚹᚠ-ᚫᚩᛡᚫᛟ-ᚷᛠ-ᚪᛡᚠᛄᚱᛏᚢᛈ.ᛏᛈ-ᛇᛞ-ᛟᛗᛇᛒᛄᚳᛈ.ᛉᛟ-ᛒᚻᚱᛄᚣ-ᚾᚱ-ᚾᛡᛈᛈ-ᛚᛉᛗᛞ-ᛟᛝ-ᚷᛁᚱᚩᚹᛗ-ᚠᛇᚣ-ᚣᛝᛒ-ᛁ-ᚠᚾᚹᚢ-ᛠᚾᛈᚠᚻ.ᚫᛋᛄᚪᚻ-ᛒᛖᛋᚻᛠ-ᛄᛗ-ᛟᛡᚹᚪᛡ-ᛄᛋᛖᚢᛗ-ᛏᛖᛉᚪ-ᛞᛟᛉᚾᚠ-ᚱᛡᛒᛚᚩᛈᛝ-ᛋᛄᛚᛗ-ᛞᚱᛗᛗ-ᛒᛈ-ᛁᛉᚱᛄᛝ.ᛋᛇᚪ-ᛗᚠᚻᚣᚹᛉᛞ-ᛡᛁᚷᚪ-ᚩᚱ-ᚪᚾᚹᛇᛋᛞᛄᚷ-ᛡ-ᛖᚫᛄ-ᛞᛟᛁᚻᚹᛝ-ᛠᛈᛏ-ᚪᛗᛗᛚᛚᚪᛞ.ᛁᛠᛈᚷᛞ-ᛗᚣᛄᚳᚹᛚ-ᚻᛋᛟᛗ-ᚣᚫᛝᛚ-ᛠᛁᛝᛝᚪ-ᚳᛗ-ᚢᚫᛋ-ᛉᛠᚱ-ᛇᛡᛄᚻᛗᚾ-ᚻᛗᛝᛚ-ᛇᛞ-ᛟᚢᚣᚪᚷᚱ-ᛡᚷ-ᚷᛠ-ᛚᚻᛒ.ᛡᛒ-ᚩᛁᛄ-ᛗᛟᛉᚩᚣ-ᛞᚩ-ᚳᛗ-ᚾᛗᚩ-ᚷᛠ-ᛚᚱᚠᚷ-ᛁᚫᛗᛉ-ᛁᛠᚹᛚ-ᛖᛝᚾᛟᛗᚾ-ᛄᚾ-ᚾᚳᛚᛝ-ᛡ-ᚷᛞᛗᚱᚻᚩ-ᛗᛞᛠᚫᛞ-ᛞᚱᛗᛗ-ᚣᚪ-ᛗᛉᚢᛞᛇᚹ-ᛟᚱᛏᚱᛟᚢᛉᛗᛚᛈᛉᛝ.ᛏᛖ-ᛗᛋᚣ-ᚹᛁ-ᚹᛝ-ᛋᛇᛄᚳᛁᛋᛝ.ᛄᛚᚹ-ᚷᚠᛝ-ᚫᚷᛚᛡᛁᛡ.ᛖᚠᚣ-ᛉᛝᚻᛄᚾᛈᚠ-ᛉᚣ-ᛚᛄᛞᛝᛞᚪ-ᚩᛈ-ᚻᛟ-ᛖᚻᚱᚹ-ᛚᚷᚳ-ᛒᛈᛏᚻ-ᚠᛋᛠᚣᛋᚠ-ᛏᚷᛈᚪᛒ.')
- # orig = RuneText('ᛇᚦ-ᛒᛏᚣᚳ-ᛇᛚᛉ-ᛄᛚᚦᚪ-ᛋᚱᛉᚦ-ᚦᛄᚻ-ᛉᛗ-ᛏᛞᛋ-ᚣᚾ-ᚣᛟᛇᛈᛟᛚ-ᛈᚹᛚᚪᛗ-ᚪᛉᛁᛇᛝᚢᚱᛉ.ᛞᛄ-ᚻᛠᚪᛚ.ᚠᛚ-ᚩᛋᚾ-ᚫᛞᛋᛁᛞᚹᚾᚪᚪ-ᚱᛟᚻ-ᛚᚠᛚᚳᛟᚱ-ᚣᛏ-ᚹᛏᛝᚣ-ᚳᚩ-ᛄᚷᛟ-ᛖ-ᚫᚻᚦᛠ-ᛒᛠᛁ-ᛁᚩᛡ-ᛗᛉᚠᚷᛁ-ᚣᚣᛋᛇᛗᛠᚹ.ᛇᚪ-ᛇᛉᛡ-ᛄᚾᛇᛁᛇᚫ-ᛋᚱᚹ-ᛝᚣᚦ-ᛠᛁᛄᛚᚢᛄ-ᚻᛇᛚᛟ-ᛒᛠᛒᛚ-ᚩᛈᛈ-ᚢᚻᛚ-ᛡᚾᛚ-ᛒᚦᚱᚠᚦᚫ-ᛞᚳ-ᛄᚳᚷ-ᚹᚫ-ᚱᛉᚣᛖᚱ.ᛒᛝᚹ-ᛟᚳᚫᚹᛈᚢ-ᚱᛋᛒ-ᚷᚦᚳᛏᛏᛠᚹ-ᚱᚣᛞ-ᚣᛠᛄ-ᛋ-qᚪᛚᚾᛖᛄᚪ-ᛇᚻᛖ-ᛏᛠᛈ-ᛝᛉᚾᚳ-ᛋᚾᚹᚦᚾ-ᚣᛞᛝᚣ-ᛠᛠᛡ-ᛉᛁᛚᚢᚩ.ᛗᛉᚦ-ᛒᛝᛇᛠᛟ-ᛁᛟᛏ-ᛠᛏᛄ-ᚫᚳᛉᛝᛖᚠ-ᛇᚠ.ᛄᛄᛝᛟᛡᛟ-ᛠᛖᚫ-ᚦᛏᛠᛗ-ᛁᛏᚩᛒᛡ-ᛝᛟ-ᛉᚠᛇᚷᛗᛠ-ᚠᛖ-ᚳᛖᛖᚾᛠᛁᚪᛟ-ᛉᚣ-ᚢᛁ.ᛒᛏ.ᛒᛠ-ᛠᛁᚢᛗ-ᛞᛟᛋᛠᚷᚠᛇᚫ-ᛏᚪ-ᛇᚦ-ᛒᚪᛟᚩᛗ.ᛟᚳᛇ-ᛞᛞ-ᛋᚱᛁᛋᚦ-ᛇᛒ-ᚳᛒᛟ-ᚳᛟᚳᚷᛇ.ᛗᛉᚦ-ᛞᚦᛉᛈᛚᛈᛚᛁᚢ-ᚳᛞᛡᛝᚻᚷ-ᛞᚪ-ᚳᛟᚳᛁᛟᛞ-')
+ with open(LP.path.page(fname), 'r') as f:
+ orig = LP.RuneText(f.read())
+ # orig = LP.RuneText('ᛄᚹᚻᛗᛋᚪ-ᛋᛁᚫᛇ-ᛋᛠᚾᛞ-ᛇᛞ-ᛞᚾᚣᚹᛗ.ᛞᛈ-ᛝᛚᚳᚾᛗᚾᚣ-ᛖᛝᛖᚦᚣᚢ-ᚱᚻᛁᚠ-ᛟᛝ-ᛚᛖᚫᛋᛚᚳᛋᛇ.ᚣᚾᚻᛄᚾᚳᛡ-ᚷᚳᛝ-ᛈᛝ-ᛡᚷᚦᚷᛖ.ᚻᛠᚣ-ᛄᛞᚹᛒ-ᛇᛄᛝᚩᛟ.ᛗᛠᚣᛋᛖᛚᚠ-ᚾᚫᛁ-ᛄᚹᚻᚻᛚᛈᚹᚠ-ᚫᚩᛡᚫᛟ-ᚷᛠ-ᚪᛡᚠᛄᚱᛏᚢᛈ.ᛏᛈ-ᛇᛞ-ᛟᛗᛇᛒᛄᚳᛈ.ᛉᛟ-ᛒᚻᚱᛄᚣ-ᚾᚱ-ᚾᛡᛈᛈ-ᛚᛉᛗᛞ-ᛟᛝ-ᚷᛁᚱᚩᚹᛗ-ᚠᛇᚣ-ᚣᛝᛒ-ᛁ-ᚠᚾᚹᚢ-ᛠᚾᛈᚠᚻ.ᚫᛋᛄᚪᚻ-ᛒᛖᛋᚻᛠ-ᛄᛗ-ᛟᛡᚹᚪᛡ-ᛄᛋᛖᚢᛗ-ᛏᛖᛉᚪ-ᛞᛟᛉᚾᚠ-ᚱᛡᛒᛚᚩᛈᛝ-ᛋᛄᛚᛗ-ᛞᚱᛗᛗ-ᛒᛈ-ᛁᛉᚱᛄᛝ.ᛋᛇᚪ-ᛗᚠᚻᚣᚹᛉᛞ-ᛡᛁᚷᚪ-ᚩᚱ-ᚪᚾᚹᛇᛋᛞᛄᚷ-ᛡ-ᛖᚫᛄ-ᛞᛟᛁᚻᚹᛝ-ᛠᛈᛏ-ᚪᛗᛗᛚᛚᚪᛞ.ᛁᛠᛈᚷᛞ-ᛗᚣᛄᚳᚹᛚ-ᚻᛋᛟᛗ-ᚣᚫᛝᛚ-ᛠᛁᛝᛝᚪ-ᚳᛗ-ᚢᚫᛋ-ᛉᛠᚱ-ᛇᛡᛄᚻᛗᚾ-ᚻᛗᛝᛚ-ᛇᛞ-ᛟᚢᚣᚪᚷᚱ-ᛡᚷ-ᚷᛠ-ᛚᚻᛒ.ᛡᛒ-ᚩᛁᛄ-ᛗᛟᛉᚩᚣ-ᛞᚩ-ᚳᛗ-ᚾᛗᚩ-ᚷᛠ-ᛚᚱᚠᚷ-ᛁᚫᛗᛉ-ᛁᛠᚹᛚ-ᛖᛝᚾᛟᛗᚾ-ᛄᚾ-ᚾᚳᛚᛝ-ᛡ-ᚷᛞᛗᚱᚻᚩ-ᛗᛞᛠᚫᛞ-ᛞᚱᛗᛗ-ᚣᚪ-ᛗᛉᚢᛞᛇᚹ-ᛟᚱᛏᚱᛟᚢᛉᛗᛚᛈᛉᛝ.ᛏᛖ-ᛗᛋᚣ-ᚹᛁ-ᚹᛝ-ᛋᛇᛄᚳᛁᛋᛝ.ᛄᛚᚹ-ᚷᚠᛝ-ᚫᚷᛚᛡᛁᛡ.ᛖᚠᚣ-ᛉᛝᚻᛄᚾᛈᚠ-ᛉᚣ-ᛚᛄᛞᛝᛞᚪ-ᚩᛈ-ᚻᛟ-ᛖᚻᚱᚹ-ᛚᚷᚳ-ᛒᛈᛏᚻ-ᚠᛋᛠᚣᛋᚠ-ᛏᚷᛈᚪᛒ.')
+ # orig = LP.RuneText('ᛇᚦ-ᛒᛏᚣᚳ-ᛇᛚᛉ-ᛄᛚᚦᚪ-ᛋᚱᛉᚦ-ᚦᛄᚻ-ᛉᛗ-ᛏᛞᛋ-ᚣᚾ-ᚣᛟᛇᛈᛟᛚ-ᛈᚹᛚᚪᛗ-ᚪᛉᛁᛇᛝᚢᚱᛉ.ᛞᛄ-ᚻᛠᚪᛚ.ᚠᛚ-ᚩᛋᚾ-ᚫᛞᛋᛁᛞᚹᚾᚪᚪ-ᚱᛟᚻ-ᛚᚠᛚᚳᛟᚱ-ᚣᛏ-ᚹᛏᛝᚣ-ᚳᚩ-ᛄᚷᛟ-ᛖ-ᚫᚻᚦᛠ-ᛒᛠᛁ-ᛁᚩᛡ-ᛗᛉᚠᚷᛁ-ᚣᚣᛋᛇᛗᛠᚹ.ᛇᚪ-ᛇᛉᛡ-ᛄᚾᛇᛁᛇᚫ-ᛋᚱᚹ-ᛝᚣᚦ-ᛠᛁᛄᛚᚢᛄ-ᚻᛇᛚᛟ-ᛒᛠᛒᛚ-ᚩᛈᛈ-ᚢᚻᛚ-ᛡᚾᛚ-ᛒᚦᚱᚠᚦᚫ-ᛞᚳ-ᛄᚳᚷ-ᚹᚫ-ᚱᛉᚣᛖᚱ.ᛒᛝᚹ-ᛟᚳᚫᚹᛈᚢ-ᚱᛋᛒ-ᚷᚦᚳᛏᛏᛠᚹ-ᚱᚣᛞ-ᚣᛠᛄ-ᛋ-qᚪᛚᚾᛖᛄᚪ-ᛇᚻᛖ-ᛏᛠᛈ-ᛝᛉᚾᚳ-ᛋᚾᚹᚦᚾ-ᚣᛞᛝᚣ-ᛠᛠᛡ-ᛉᛁᛚᚢᚩ.ᛗᛉᚦ-ᛒᛝᛇᛠᛟ-ᛁᛟᛏ-ᛠᛏᛄ-ᚫᚳᛉᛝᛖᚠ-ᛇᚠ.ᛄᛄᛝᛟᛡᛟ-ᛠᛖᚫ-ᚦᛏᛠᛗ-ᛁᛏᚩᛒᛡ-ᛝᛟ-ᛉᚠᛇᚷᛗᛠ-ᚠᛖ-ᚳᛖᛖᚾᛠᛁᚪᛟ-ᛉᚣ-ᚢᛁ.ᛒᛏ.ᛒᛠ-ᛠᛁᚢᛗ-ᛞᛟᛋᛠᚷᚠᛇᚫ-ᛏᚪ-ᛇᚦ-ᛒᚪᛟᚩᛗ.ᛟᚳᛇ-ᛞᛞ-ᛋᚱᛁᛋᚦ-ᛇᛒ-ᚳᛒᛟ-ᚳᛟᚳᚷᛇ.ᛗᛉᚦ-ᛞᚦᛉᛈᛚᛈᛚᛁᚢ-ᚳᛞᛡᛝᚻᚷ-ᛞᚪ-ᚳᛟᚳᛁᛟᛞ-')
data = orig.index
if False: # longest uninterrupted text
- pos, lg = InterruptDB.longest_no_interrupt(data, interrupt=0, irpmax=0)
+ pos, lg = LP.InterruptDB.longest_no_interrupt(data, interrupt=0, irpmax=0)
data = data[pos:pos + lg]
else: # from the beginning
data = data[:170]
@@ -83,7 +76,7 @@ def pattern_solver(fname, irp=0):
data = [x for x in data if x != 29]
def fn_similarity(x):
- return Probability(x).similarity()
+ return LP.Probability(x).similarity()
def fn_pattern_mirror(x, kl):
for i in range(10000): # mirrored, 012210012210 or 012101210
@@ -92,7 +85,7 @@ def pattern_solver(fname, irp=0):
yield from x[::-1][1:-1]
print(fname)
- gr = GuessPattern(data)
+ gr = LP.GuessPattern(data)
for kl in range(3, 19):
# for pattern_shift in range(1):
# fn_pattern = fn_pattern_mirror
@@ -106,29 +99,29 @@ def pattern_solver(fname, irp=0):
# Find proper pattern
res = []
for offset in range(kl): # up to keylen offset
- mask = GuessPattern.pattern(kl, fn_pattern)
+ mask = LP.GuessPattern.pattern(kl, fn_pattern)
parts = gr.split(kl, mask, offset)
- score = sum(Probability(x).IC() for x in parts) / kl
+ score = sum(LP.Probability(x).IC() for x in parts) / kl
if score > 1.6 and score < 2.1:
res.append((score, parts, offset))
# Find best matching key for pattern
for score, parts, off in res:
- sc, solution = GuessPattern.guess(parts, fn_similarity)
+ sc, solution = LP.GuessPattern.guess(parts, fn_similarity)
if sc < 0.1:
fmt = 'kl: {}, pattern-n: {}, IoC: {:.3f}, dist: {:.4f}, offset: {}, key: {}'
print(fmt.format(kl, pattern_shift, score, sc, off,
- RuneText(solution).text))
+ LP.RuneText(solution).text))
solved = gr.zip(fn_pattern(solution, kl), off)
for i in data_i:
solved.insert(i, 29)
- print(' ', RuneText(solved).text)
+ print(' ', LP.RuneText(solved).text)
#########################################
# main
#########################################
-db = InterruptDB.load('db_norm')
+db = LP.InterruptDB.load('db_norm')
# IOC_MIN_SCORE = 1.4 # for db_high
IOC_MIN_SCORE = 0.55 # for db_norm
@@ -166,5 +159,5 @@ for fname in [
maxscore = max(x[1] for x in db[fname])
print('No candidates. Highest score is only', maxscore)
continue
- break_cipher(fname, candidates, AffineSolver, GuessAffine)
- break_cipher(fname, candidates, VigenereSolver, GuessVigenere)
+ break_cipher(fname, candidates, LP.AffineSolver, LP.GuessAffine)
+ break_cipher(fname, candidates, LP.VigenereSolver, LP.GuessVigenere)
diff --git a/solver.py b/solver.py
index 297f9bf..d5a3953 100755
--- a/solver.py
+++ b/solver.py
@@ -1,22 +1,20 @@
#!/usr/bin/env python3
-from RuneSolver import VigenereSolver, SequenceSolver
-from RuneText import Rune, RuneText
-from lib import elliptic_curve
+import LP
import sys
import itertools
def load_sequence_file(fname):
- with open(fname, 'r') as f:
+ with open(LP.path.data(fname), 'r') as f:
return [int(x) for x in f.readlines()]
-PRIMES = load_sequence_file('data/seq_primes_1000.txt')
-PRIMES_3301 = load_sequence_file('data/seq_primes_3301.txt')
-NOT_PRIMES = load_sequence_file('data/seq_not_primes.txt')
-FIBONACCI = load_sequence_file('data/seq_fibonacci.txt')
-LUCAS = load_sequence_file('data/seq_lucas_numbers.txt')
-MOEBIUS = load_sequence_file('data/seq_moebius.txt')
+PRIMES = load_sequence_file('seq_primes_1000')
+PRIMES_3301 = load_sequence_file('seq_primes_3301')
+NOT_PRIMES = load_sequence_file('seq_not_primes')
+FIBONACCI = load_sequence_file('seq_fibonacci')
+LUCAS = load_sequence_file('seq_lucas_numbers')
+MOEBIUS = load_sequence_file('seq_moebius')
def print_all_solved():
@@ -46,13 +44,13 @@ def print_all_solved():
slvr.INTERRUPT = 'ᚠ'
slvr.INTERRUPT_POS = [4]
- def solve(path, fn_solution, solver=VigenereSolver):
+ def solve(fname, fn_solution, solver=LP.VigenereSolver):
slvr = solver()
slvr.output.COLORS = False
slvr.output.QUIET = True # or use -v/-q while calling
- slvr.input.load(file=f'pages/{path}.txt')
+ slvr.input.load(file=LP.path.page(fname))
fn_solution(slvr)
- print(f'pages/{path}.txt')
+ print(f'pages/{fname}.txt')
print()
slvr.run()
print()
@@ -64,78 +62,62 @@ def print_all_solved():
solve('0_loss_of_divinity', plain)
solve('jpg107-167', solution_jpg107_167)
solve('jpg229', plain)
- solve('p56_an_end', solution_p56_end, solver=SequenceSolver)
+ solve('p56_an_end', solution_p56_end, solver=LP.SequenceSolver)
solve('p57_parable', plain)
def play_around():
- slvr = VigenereSolver()
+ slvr = LP.VigenereSolver()
slvr.output.COLORS = False
slvr.output.QUIET = True
slvr.KEY_DATA = []
vowels = 'ᚢᚩᛁᛇᛖᛟᚪᚫᛡᛠ'
- for uuu in ['0-2', '3-7', '8-14', '15-22', '23-26', '27-32', '33-39', '40-53', '54-55']:
- slvr.input.load(file=f'pages/p{uuu}.txt')
+ for uuu in LP.FILES_UNSOLVED:
+ slvr.input.load(file=LP.path.page(uuu))
print(uuu)
print('word count:', sum(len(x) for x in slvr.input.words.values()))
a = [1 if x.rune in vowels else 0 for x in slvr.input.runes_no_whitespace()]
b = [a[i:i + 5] for i in range(0, len(a), 5)]
c = [int(''.join(str(y) for y in x), 2) for x in b]
# print('-'.join(str(x) for x in c))
- # print(''.join(Rune(i=x).text for x in c))
+ # print(LP.RuneText(c).text)
# print(''.join('ABCDEFGHIJKLMNOPQRSTUVWXYZ___...'[x] for x in c))
# slvr.run()
def try_totient_on_unsolved():
- slvr = SequenceSolver()
+ slvr = LP.SequenceSolver()
slvr.output.QUIET = True
slvr.output.BREAK_MODE = '' # disable line breaks
# slvr.INTERRUPT = 'ᛝ'
# slvr.INTERRUPT_POS = [1]
# for uuu in ['15-22']:
- for uuu in ['0-2', '3-7', '8-14', '15-22', '23-26', '27-32', '33-39', '40-53', '54-55']:
+ for uuu in LP.FILES_UNSOLVED:
print()
print(uuu)
- with open(f'pages/p{uuu}.txt', 'r') as f:
- slvr.input.load(RuneText(f.read()[:15]))
- # alldata = slvr.input.runes_no_whitespace() + [Rune(i=29)]
-
- def b60(x):
- v = x % 60
- return v if v < 29 else 60 - v
+ slvr.input.load(file=LP.path.page(uuu), limit=25)
+ # alldata = slvr.input.runes_no_whitespace() + [LP.Rune(i=29)]
def ec(r, i):
- p1, p2 = elliptic_curve(i, 149, 263, 3299)
+ p1, p2 = LP.utils.elliptic_curve(i, 149, 263, 3299)
if p1 is None:
return r.index
return r.index + p1 % 29
- # for p in PRIMES[:500]:
- # print(p)
- # for z in range(29):
- # def fn(i, x):
- # return (x + alldata[i - 1].index + z) % 29
- # if fn(0, alldata[0].index) not in [10, 24]:
- # continue
- # slvr.FN = lambda i, r: Rune(i=fn(i, r.index))
- # slvr.run()
+
for z in range(29):
- # slvr.FN = lambda i, r: r - z # ((i + z) // 3)
# slvr.FN = lambda i, r: r - PRIMES[i] + z
- # slvr.FN = lambda i, r: Rune(i=b60(r.prime) + z % 29)
- # slvr.FN = lambda i, r: Rune(i=((r.prime + alldata[i + 1].prime) + z) % 60 // 2)
- # slvr.FN = lambda i, r: Rune(i=(3301 * r.index + z) % 29)
- slvr.FN = lambda i, r: Rune(i=(ec(r, i) + z) % 29)
- # slvr.FN = lambda i, r: Rune(i=(r.prime - PRIMES[FIBONACCI[i]] + z) % 29)
- # slvr.FN = lambda i, r: Rune(i=(r.prime ** i + z) % 29)
+ # slvr.FN = lambda i, r: LP.Rune(i=((r.prime + alldata[i + 1].prime) + z) % 60 // 2)
+ # slvr.FN = lambda i, r: LP.Rune(i=(r.prime - PRIMES[FIBONACCI[i]] + z) % 29)
+ # slvr.FN = lambda i, r: LP.Rune(i=(r.prime ** i + z) % 29)
+ slvr.FN = lambda i, r: LP.Rune(i=(ec(r, i) + z) % 29)
slvr.run()
-def find_oeis(irp=0, invert=False, offset=0):
+def find_oeis(irp=0, invert=False, offset=0, allow_fails=1, min_match=2):
def trim_orig_oeis(minlen=15, trim=40):
# download and unzip: https://oeis.org/stripped.gz
- with open('data/oeis_orig.txt', 'r') as f_in:
- with open('data/oeis.txt', 'w') as f_out:
+ with open(LP.path.data('oeis_orig'), 'r') as f_in:
+ with open(LP.path.data('oeis'), 'w') as f_out:
for line in f_in.readlines():
if line[0] == '#':
continue
@@ -146,64 +128,79 @@ def find_oeis(irp=0, invert=False, offset=0):
f_out.write(name + ',' + ','.join(vals) + '\n')
# trim_orig_oeis() # create db if not present already
- with open('data/oeis.txt', 'r') as f:
+ with open(LP.path.data('oeis'), 'r') as f:
seqs = []
for line in f.readlines():
- name, *vals = line.split(',')
- vals = [int(x) for x in vals]
- seqs.append([name] + vals)
+ vals = line.split(',')
+ seqs.append((vals[0], list(map(int, vals[1:]))))
- RUNES = 'ᚠᚢᚦᚩᚱᚳᚷᚹᚻᚾᛁᛄᛇᛈᛉᛋᛏᛒᛖᛗᛚᛝᛟᛞᚪᚫᚣᛡᛠ'
words = [set()] * 13
- words[1] = set(x for x in RUNES)
+ words[1] = set(x for x in LP.RUNES)
for i in range(2, 13): # since 12 is the longest word
- with open(f'data/dictionary_{i}.txt', 'r') as f:
+ with open(LP.path.data(f'dictionary_{i}'), 'r') as f:
words[i] = set(x.strip() for x in f.readlines())
for uuu, wlen in {
- '0-2': [8, 5, 4, 3, 3, 11, 5, 4, 3, 3],
- '3-7': [2, 11, 3, 4, 7, 7, 7, 4, 6],
- '8-14': [4, 8, 3, 2, 3, 9, 4, 3, 4, 2, 2],
- '15-22': [4, 5, 4, 2, 5, 4, 5, 6, 5, 6, 3, 3],
- '23-26': [2, 6, 3, 4, 8, 3, 3, 7, 5, 5],
- '27-32': [3, 12, 4, 7, 2, 3, 3, 2, 1, 3, 4],
- '33-39': [2, 8, 2, 9, 6, 3, 3, 5, 3, 2],
- '40-53': [3, 5, 5, 4, 3, 5, 4, 2, 12, 3, 3, 2],
- '54-55': [1, 8, 8, 3, 6, 2, 5, 3, 2, 3, 5, 7],
- # '56_an_end': [2, 3, 5, 2, 4, 3, 4, 6, 1, 4, 3, 6, 2],
+ 'p0-2': [8, 5, 4, 3, 3, 11, 5, 4, 3, 3],
+ 'p3-7': [2, 11, 3, 4, 7, 7, 7, 4, 6],
+ 'p8-14': [4, 8, 3, 2, 3, 9, 4, 3, 4, 2, 2],
+ 'p15-22': [4, 5, 4, 2, 5, 4, 5, 6, 5, 6, 3, 3],
+ 'p23-26': [2, 6, 3, 4, 8, 3, 3, 7, 5, 5],
+ 'p27-32': [3, 12, 4, 7, 2, 3, 3, 2, 1, 3, 4],
+ 'p33-39': [2, 8, 2, 9, 6, 3, 3, 5, 3, 2],
+ 'p40-53': [3, 5, 5, 4, 3, 5, 4, 2, 12, 3, 3, 2],
+ 'p54-55': [1, 8, 8, 3, 6, 2, 5, 3, 2, 3, 5, 7],
+ # 'p56_an_end': [2, 3, 5, 2, 4, 3, 4, 6, 1, 4, 3, 6, 2],
}.items():
- minwords = sum(wlen[:2]) # must match at least n words
splits = [(0, 0, 0)]
for x in wlen:
splits.append((splits[-1][1], splits[-1][1] + x))
splits = splits[1:]
print()
print(uuu)
- with open(f'pages/p{uuu}.txt', 'r') as f:
- data = RuneText(f.read()[:120]).index_no_whitespace
+ with open(LP.path.page(uuu), 'r') as f:
+ data = LP.RuneText(f.read()[:120]).index_rune_only
irps = [i for i, x in enumerate(data[:splits[-1][1]]) if x == irp]
- irps.reverse()
+ irps.reverse() # insert -1 starting with the last
if invert:
data = [28 - x for x in data]
- for oeis, *v in seqs: # 390k
- v = v[offset:]
- if len(v) < minwords:
+ min_len = sum(wlen[:2]) # must match at least n words
+ data_len = len(data)
+ for oeis, vals in seqs: # 390k
+ vals = vals[offset:]
+ if len(vals) < min_len:
continue
- cases = [x for x in irps if x < len(v)]
+ cases = [x for x in irps if x < len(vals)]
for i in range(len(cases) + 1):
for comb in itertools.combinations(cases, i): # 2^3
- res = v[:]
+ res = vals[:]
for z in comb:
res.insert(z, -1) # insert interrupts
+ shortest = min(data_len, len(res))
+
for s in range(29):
- who = ''.join(RUNES[x if y == -1 else (x - y - s) % 29]
- for x, y in zip(data, res))
- active = [x for x in splits if x[1] <= len(who)]
- bag = [who[x:y] for x, y in active]
- if all(w in words[len(w)] for w in bag):
- print(oeis.split()[0], 'shift:', s, 'irps:', comb)
- print(' ', ' '.join(RuneText(w).text for w in bag))
+ failed = 0
+ full = []
+ clen = 0
+ for a, b in splits:
+ if b > shortest:
+ break
+ nums = [x if y == -1 else (x - y - s) % 29
+ for x, y in zip(data[a:b], res[a:b])]
+ word = ''.join(LP.RUNES[x] for x in nums)
+ if word in words[len(nums)]:
+ clen += len(nums)
+ else:
+ failed += 1
+ if failed > allow_fails:
+ break
+ full.append(LP.RuneText(nums).text)
+
+ if failed > allow_fails or clen < min_match:
+ continue # too many failed
+ print(oeis.split()[0], 'shift:', s, 'irps:', comb)
+ print(' ', ' '.join(full))
if '-s' in sys.argv: # print [s]olved
@@ -213,4 +210,4 @@ else:
# try_totient_on_unsolved()
# for i in range(0, 4):
# print('offset:', i)
- # find_oeis(irp=0, invert=False, offset=i)
+ # find_oeis(irp=0, invert=False, offset=i, allow_fails=1, min_match=10)
diff --git a/tmp/0_welcome_0.8844.GuessVigenere.0_8.txt b/tmp/0_welcome_0.8844.GuessVigenere.0_8.txt
new file mode 100644
index 0000000..463c639
--- /dev/null
+++ b/tmp/0_welcome_0.8844.GuessVigenere.0_8.txt
@@ -0,0 +1,17 @@
+0, 8, 0.9018, 0.0193, [23, 10, 1, 10, 9, 10, 16, 26], [4, 5, 6, 7, 9, 12, 14, 18, 20]
+WELCOME#
+WELCOME,PILGRIM,TO THE GREAT JOURNEY TOWARD THE END OF ALL THNGS.
+IT IS NOT AN EASY TRIP,BUT FOR THOSE WHO FIND THEIR WAY HERE IT IS A NECESSARY ONE.
+ALONG THE WAY YOU WILL FIND AN END TO ALL STRUGGFM UML THNGEAMMOS,TFUR INNOCENCE,YOUR ILLUSIANS,YOUR CERTAINTY,AND YOUR REALITY.
+ULTIMATELY,YOU WILL DISCOUER AN END TO SELF.
+IT IS THROUGH THIS PILGRIMAGE THAT WE SHAPE OURSELUES AND OUR REALITIES.
+JOURNEY DEEP WITHIN AND YOU WILL ARRIUE OUTSIDE.
+LICE THE INSTAR,IT IS ONLY THROUGH GONG WITHIN THAT WE MAY EMERGE#
+WIDSOM#
+YOU ARE A BENG UNTO YOURSELF.
+YOU ARE A LAW UNTO YOURSELF.
+EACH INTELLIGENCE IS HOLY.
+GTP SNGM AEXM RMNGMX RC JTFB#
+#
+AEH REAEFPNGGSNGEA#
+HTEAIAEHB TGXP DHH NHDM#
diff --git a/tmp/jpg107-167_0.7102.GuessVigenere.0_13.txt b/tmp/jpg107-167_0.7102.GuessVigenere.0_13.txt
new file mode 100644
index 0000000..079a8c6
--- /dev/null
+++ b/tmp/jpg107-167_0.7102.GuessVigenere.0_13.txt
@@ -0,0 +1,8 @@
+0, 13, 0.7420, 0.0429, [21, 10, 4, 0, 1, 19, 21, 18, 4, 18, 24, 0, 18], [2, 4]
+O COAN#
+DNRNG A CESDON,THE MOSTEE EXNGLAINETH THE I.
+"TE I ES THE UOECE OF TE CERCUMITSOAEAIA,"MS CUXOE.
+EGFD INXFL WO EA XIAEACOAI AEX SRMADNGW EOEII JGP NCPS;PT UNUIIAS EOXTIA"NIA FD A UOACE ENSIDE COUR OEEAD".
+"E DON'T HOUE A SOIPE IN MY TEAD," THBUGTT THE STNDENU,ANTH HE RAIDED HAS HOND TO TYLL THO MADTER.
+THE IAASTOR SAOPPED IE STSDEBT,AND SOID"THO UOECE THAT MUST FAITH YOU HANE NO SOIPE IN YONR HEAH;IS IE I.
+"AND IE STSDEBTS WERY ENLAGHAENED#