Add double stepping, re-work component files, fix bugs.
This commit is contained in:
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# prettier insists on making the array literals very long and thin. Aside from taking up
|
||||||
|
# space, it also makes it hard to tell, at a glance, if the array contains 26
|
||||||
|
# characters. I've opted to do my own little formatting in this file.
|
||||||
|
components.ts
|
187
src/components.ts
Normal file
187
src/components.ts
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
export type RotorComponent = {
|
||||||
|
name: M3RotorName | M4RotorName,
|
||||||
|
values: string[],
|
||||||
|
turnovers: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ReflectorComponent = {
|
||||||
|
name: M3ReflectorName | M4ReflectorName,
|
||||||
|
reflector: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export type M3RotorName = "I" | "II" | "III" | "IV" | "V" | "VI" | "VII" | "VIII";
|
||||||
|
export type M3ReflectorName = "A" | "B" | "C";
|
||||||
|
export type M4RotorName = "Beta" | "Gamma";
|
||||||
|
export type M4ReflectorName = "B Thin" | "C Thin";
|
||||||
|
|
||||||
|
export const m3Rotots: Record<M3RotorName, RotorComponent> = {
|
||||||
|
I: {
|
||||||
|
name: "I",
|
||||||
|
values: [
|
||||||
|
"E", "K", "M", "F", "L", "G",
|
||||||
|
"D", "Q", "V", "Z", "N", "T",
|
||||||
|
"O", "W", "Y", "H", "X", "U",
|
||||||
|
"S", "P", "A", "I", "B", "R",
|
||||||
|
"C", "J",
|
||||||
|
],
|
||||||
|
turnovers: ["R"],
|
||||||
|
},
|
||||||
|
II: {
|
||||||
|
name: "II",
|
||||||
|
values: [
|
||||||
|
"A", "J", "D", "K", "S", "I",
|
||||||
|
"R", "U", "X", "B", "L", "H",
|
||||||
|
"W", "T", "M", "C", "Q", "G",
|
||||||
|
"Z", "N", "P", "Y", "F", "V",
|
||||||
|
"O", "E",
|
||||||
|
],
|
||||||
|
turnovers: ["F"],
|
||||||
|
},
|
||||||
|
III: {
|
||||||
|
name: "III",
|
||||||
|
values: [
|
||||||
|
"B", "D", "F", "H", "J", "L",
|
||||||
|
"C", "P", "R", "T", "X", "V",
|
||||||
|
"Z", "N", "Y", "E", "I", "W",
|
||||||
|
"G", "A", "K", "M", "U", "S",
|
||||||
|
"Q", "O",
|
||||||
|
],
|
||||||
|
turnovers: ["W"],
|
||||||
|
},
|
||||||
|
IV: {
|
||||||
|
name: "IV",
|
||||||
|
values: [
|
||||||
|
"E", "S", "O", "V", "P", "Z",
|
||||||
|
"J", "A", "Y", "Q", "U", "I",
|
||||||
|
"R", "H", "X", "L", "N", "F",
|
||||||
|
"T", "G", "K", "D", "C", "M",
|
||||||
|
"W", "B",
|
||||||
|
],
|
||||||
|
turnovers: ["K"],
|
||||||
|
},
|
||||||
|
V: {
|
||||||
|
name: "V",
|
||||||
|
values: [
|
||||||
|
"V", "Z", "B", "R", "G", "I",
|
||||||
|
"T", "Y", "U", "P", "S", "D",
|
||||||
|
"N", "H", "L", "X", "A", "W",
|
||||||
|
"M", "J", "Q", "O", "F", "E",
|
||||||
|
"C", "K",
|
||||||
|
],
|
||||||
|
turnovers: ["A"],
|
||||||
|
},
|
||||||
|
VI: {
|
||||||
|
name: "VI",
|
||||||
|
values: [
|
||||||
|
"J", "P", "G", "V", "O", "U",
|
||||||
|
"M", "F", "Y", "Q", "B", "E",
|
||||||
|
"N", "H", "Z", "R", "D", "K",
|
||||||
|
"A", "S", "X", "L", "I", "C",
|
||||||
|
"T", "W",
|
||||||
|
],
|
||||||
|
turnovers: ["A", "N"],
|
||||||
|
},
|
||||||
|
VII: {
|
||||||
|
name: "VII",
|
||||||
|
values: [
|
||||||
|
"N", "Z", "J", "H", "G", "R",
|
||||||
|
"C", "X", "M", "Y", "S", "W",
|
||||||
|
"B", "O", "U", "F", "A", "I",
|
||||||
|
"V", "L", "P", "E", "K", "Q",
|
||||||
|
"D", "T",
|
||||||
|
],
|
||||||
|
turnovers: ["A", "N"],
|
||||||
|
},
|
||||||
|
VIII: {
|
||||||
|
name: "VIII",
|
||||||
|
values: [
|
||||||
|
"F", "K", "Q", "H", "T", "L",
|
||||||
|
"X", "O", "C", "B", "J", "S",
|
||||||
|
"P", "D", "Z", "R", "A", "M",
|
||||||
|
"E", "W", "N", "I", "U", "Y",
|
||||||
|
"G", "V",
|
||||||
|
],
|
||||||
|
turnovers: ["A", "N"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const m3Reflectors: Record<M3ReflectorName, ReflectorComponent> = {
|
||||||
|
A: {
|
||||||
|
name: "A",
|
||||||
|
reflector: [
|
||||||
|
"E", "J", "M", "Z", "A", "L",
|
||||||
|
"Y", "X", "V", "B", "W", "F",
|
||||||
|
"C", "R", "Q", "U", "O", "N",
|
||||||
|
"T", "S", "P", "I", "K", "H",
|
||||||
|
"G", "D",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
B: {
|
||||||
|
name: "B",
|
||||||
|
reflector: [
|
||||||
|
"Y", "R", "U", "H", "Q", "S",
|
||||||
|
"L", "D", "P", "X", "N", "G",
|
||||||
|
"O", "K", "M", "I", "E", "B",
|
||||||
|
"F", "Z", "C", "W", "V", "J",
|
||||||
|
"A", "T",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
C: {
|
||||||
|
name: "C",
|
||||||
|
reflector: [
|
||||||
|
"F", "V", "P", "J", "I", "A",
|
||||||
|
"O", "Y", "E", "D", "R", "Z",
|
||||||
|
"X", "W", "G", "C", "T", "K",
|
||||||
|
"U", "Q", "S", "B", "N", "M",
|
||||||
|
"H", "L",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const m4Rotors: Record<M4RotorName, RotorComponent> = {
|
||||||
|
Beta: {
|
||||||
|
name: "Beta",
|
||||||
|
values: [
|
||||||
|
"L", "E", "Y", "J", "V", "C",
|
||||||
|
"N", "I", "X", "W", "P", "B",
|
||||||
|
"Q", "M", "D", "R", "T", "A",
|
||||||
|
"K", "Z", "G", "F", "U", "H",
|
||||||
|
"O", "S",
|
||||||
|
],
|
||||||
|
turnovers: [],
|
||||||
|
},
|
||||||
|
Gamma: {
|
||||||
|
name: "Gamma",
|
||||||
|
values: [
|
||||||
|
"F", "S", "O", "K", "A", "N",
|
||||||
|
"U", "E", "R", "H", "M", "B",
|
||||||
|
"T", "I", "Y", "C", "W", "L",
|
||||||
|
"Q", "P", "Z", "X", "V", "G",
|
||||||
|
"J", "D",
|
||||||
|
],
|
||||||
|
turnovers: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const m4Reflectors: Record<M4ReflectorName, ReflectorComponent> = {
|
||||||
|
"B Thin": {
|
||||||
|
name: "B Thin",
|
||||||
|
reflector: [
|
||||||
|
"E", "N", "K", "Q", "A", "U",
|
||||||
|
"Y", "W", "J", "I", "C", "O",
|
||||||
|
"P", "B", "L", "M", "D", "X",
|
||||||
|
"Z", "V", "F", "T", "H", "R",
|
||||||
|
"G", "S",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"C Thin": {
|
||||||
|
name: "C Thin",
|
||||||
|
reflector: [
|
||||||
|
"R", "D", "O", "B", "J", "N",
|
||||||
|
"T", "K", "V", "E", "H", "M",
|
||||||
|
"L", "F", "C", "W", "Z", "A",
|
||||||
|
"X", "G", "Y", "I", "P", "S",
|
||||||
|
"U", "Q",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}as const;
|
11
src/index.ts
11
src/index.ts
@ -1,6 +1,11 @@
|
|||||||
import { Machine } from "./machine";
|
import { Machine } from "./machine";
|
||||||
|
|
||||||
const machine = new Machine("A", "I", "II", "III");
|
let machine = new Machine("A", "I", "II", "III");
|
||||||
|
|
||||||
console.log(machine.input("THISISAMESSAGEFORYOU"));
|
console.log("THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOG");
|
||||||
console.log("rings", machine.rings());
|
|
||||||
|
const cipher = machine.input("THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOG");
|
||||||
|
console.log(cipher);
|
||||||
|
|
||||||
|
machine = new Machine("A", "I", "II", "III");
|
||||||
|
console.log(machine.input(cipher));
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
|
|
||||||
export type RotorValues = {
|
|
||||||
name: string;
|
|
||||||
values: string[];
|
|
||||||
turnovers: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ReflectorValues = {
|
|
||||||
name: string;
|
|
||||||
reflector: Map<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function loadFile(filename: string) {
|
|
||||||
const data = fs.readFileSync(filename, "utf-8");
|
|
||||||
let row: string[] = [];
|
|
||||||
const table: string[][] = [];
|
|
||||||
let cell = "";
|
|
||||||
|
|
||||||
for (const char of data) {
|
|
||||||
if (char !== "," && char !== "\n") {
|
|
||||||
cell += char;
|
|
||||||
} else {
|
|
||||||
row.push(cell);
|
|
||||||
cell = "";
|
|
||||||
|
|
||||||
if (char === "\n") {
|
|
||||||
table.push(row);
|
|
||||||
row = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the file ends in a line break, this isn't needed but if it doesn't, then there
|
|
||||||
// is a rotor hanging out at the end that still needs to be added.
|
|
||||||
if (row.length > 0) {
|
|
||||||
row.push(cell);
|
|
||||||
table.push(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildRotors(filename: string) {
|
|
||||||
const rotors: RotorValues[] = [];
|
|
||||||
const table = loadFile(filename);
|
|
||||||
|
|
||||||
for (const [name, valueString, turnovers] of table) {
|
|
||||||
rotors.push({
|
|
||||||
name,
|
|
||||||
values: valueString.split(""),
|
|
||||||
turnovers: turnovers.split(""),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return rotors;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildReflectors(filename: string) {
|
|
||||||
const reflectors: ReflectorValues[] = [];
|
|
||||||
const table = loadFile(filename);
|
|
||||||
|
|
||||||
for (const [name, valueString] of table) {
|
|
||||||
const values = valueString.split("");
|
|
||||||
const reflector = { name, reflector: new Map() };
|
|
||||||
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
const a = String.fromCharCode("A".charCodeAt(0) + i);
|
|
||||||
const b = values[i];
|
|
||||||
|
|
||||||
reflector.reflector.set(a, b);
|
|
||||||
reflector.reflector.set(b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
reflectors.push(reflector);
|
|
||||||
}
|
|
||||||
|
|
||||||
return reflectors;
|
|
||||||
}
|
|
109
src/machine.ts
109
src/machine.ts
@ -1,29 +1,67 @@
|
|||||||
import {
|
import {
|
||||||
buildReflectors,
|
M3ReflectorName,
|
||||||
buildRotors,
|
m3Reflectors,
|
||||||
ReflectorValues,
|
M3RotorName,
|
||||||
RotorValues,
|
m3Rotots as m3Rotors,
|
||||||
} from "./loadComponentData";
|
M4ReflectorName,
|
||||||
|
m4Reflectors,
|
||||||
|
M4RotorName,
|
||||||
|
m4Rotors,
|
||||||
|
} from "./components";
|
||||||
import { Plugboard } from "./plugboard";
|
import { Plugboard } from "./plugboard";
|
||||||
import { Rotor } from "./rotors";
|
import { Rotor } from "./rotors";
|
||||||
import { checkCharacter } from "./validation";
|
import { checkCharacter } from "./validation";
|
||||||
|
|
||||||
const rotors = buildRotors("rotors.csv");
|
|
||||||
const reflectors = buildReflectors("reflectors.csv");
|
|
||||||
|
|
||||||
export class Machine {
|
export class Machine {
|
||||||
plugboard: Plugboard;
|
plugboard: Plugboard;
|
||||||
reflector: ReflectorValues;
|
reflector: Map<string, string>;
|
||||||
rotors: Rotor[];
|
rotors: Rotor[];
|
||||||
|
|
||||||
constructor(reflector: string, first: string, second: string, third: string) {
|
constructor(
|
||||||
|
reflector: M3ReflectorName,
|
||||||
|
first: M3RotorName,
|
||||||
|
second: M3RotorName,
|
||||||
|
third: M3RotorName,
|
||||||
|
);
|
||||||
|
constructor(
|
||||||
|
reflector: M4ReflectorName,
|
||||||
|
first: M3RotorName,
|
||||||
|
second: M3RotorName,
|
||||||
|
third: M3RotorName,
|
||||||
|
fourth: M4RotorName,
|
||||||
|
);
|
||||||
|
constructor(
|
||||||
|
reflector: M3ReflectorName | M4ReflectorName,
|
||||||
|
first: M3RotorName,
|
||||||
|
second: M3RotorName,
|
||||||
|
third: M3RotorName,
|
||||||
|
fourth?: M4RotorName,
|
||||||
|
) {
|
||||||
this.plugboard = new Plugboard();
|
this.plugboard = new Plugboard();
|
||||||
this.reflector = getComponent(reflectors, reflector);
|
const reflectorComponent = getComponent(
|
||||||
|
{ ...m3Reflectors, ...m4Reflectors },
|
||||||
|
reflector,
|
||||||
|
);
|
||||||
|
|
||||||
this.rotors = [];
|
this.reflector = new Map();
|
||||||
this.rotors.push(getRotor(getComponent(rotors, first)));
|
for (let i = 0; i < 26; i++) {
|
||||||
this.rotors.push(getRotor(getComponent(rotors, second)));
|
const aCharCode = "A".charCodeAt(0);
|
||||||
this.rotors.push(getRotor(getComponent(rotors, third)));
|
this.reflector.set(
|
||||||
|
String.fromCharCode(aCharCode + i),
|
||||||
|
reflectorComponent.reflector[i],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rotorComponents = [
|
||||||
|
getComponent(m3Rotors, first),
|
||||||
|
getComponent(m3Rotors, second),
|
||||||
|
getComponent(m3Rotors, third),
|
||||||
|
];
|
||||||
|
if (fourth) rotorComponents.push(getComponent(m4Rotors, fourth));
|
||||||
|
|
||||||
|
this.rotors = rotorComponents.map(
|
||||||
|
({ name, values, turnovers }) => new Rotor(name, values, turnovers),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rings(): string[] {
|
rings(): string[] {
|
||||||
@ -40,20 +78,42 @@ export class Machine {
|
|||||||
checkCharacter(char, "character");
|
checkCharacter(char, "character");
|
||||||
let turnover = true;
|
let turnover = true;
|
||||||
|
|
||||||
|
// There is a rare case when rotor three turns, and it does not cause rotor
|
||||||
|
// two to turn (double-step): on the FIRST keypress when BOTH wheels are set
|
||||||
|
// to turn. In that case, it's important not to set the second wheel as the
|
||||||
|
// third one steps, since it will already have stepped and stepping twice in
|
||||||
|
// one keypress is not possible mechanically.
|
||||||
|
let rotorTwoStepped = false;
|
||||||
|
|
||||||
// plugboard, first pass
|
// plugboard, first pass
|
||||||
let scrambled = this.plugboard.in(char);
|
let scrambled = this.plugboard.in(char);
|
||||||
|
|
||||||
// rotors, first pass (in)
|
// rotors, first pass (in)
|
||||||
for (const rotor of this.rotors) {
|
for (let i = 0; i < this.rotors.length; i++) {
|
||||||
|
const rotor = this.rotors[i];
|
||||||
if (turnover) {
|
if (turnover) {
|
||||||
turnover = rotor.rotate();
|
turnover = rotor.rotate();
|
||||||
|
|
||||||
|
if (i === 1) {
|
||||||
|
rotorTwoStepped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because of a quirk in the machines design, if the third rotor
|
||||||
|
// rotates, the second rotor also rotates. This funny action was
|
||||||
|
// called "double-stepping". Although Naval Enigma added a fourth
|
||||||
|
// rotor, it never causes the third rotor to double-step because
|
||||||
|
// the fourth rotor never actually rotated. Rotor one is not
|
||||||
|
// affected because it always rotates anyway.
|
||||||
|
if (i === 2 && !rotorTwoStepped) {
|
||||||
|
this.rotors[1].rotate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrambled = rotor.in(scrambled);
|
scrambled = rotor.in(scrambled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// reflector
|
// reflector
|
||||||
scrambled = this.reflector.reflector.get(scrambled)!;
|
scrambled = this.reflector.get(scrambled)!;
|
||||||
|
|
||||||
// rotors, second pass (out)
|
// rotors, second pass (out)
|
||||||
for (let i = this.rotors.length - 1; i >= 0; i--) {
|
for (let i = this.rotors.length - 1; i >= 0; i--) {
|
||||||
@ -63,22 +123,23 @@ export class Machine {
|
|||||||
|
|
||||||
// plugboard, second pass
|
// plugboard, second pass
|
||||||
output += this.plugboard.in(scrambled);
|
output += this.plugboard.in(scrambled);
|
||||||
|
|
||||||
|
let state = "";
|
||||||
|
for (const r of this.rotors) {
|
||||||
|
state += ` ${r.name}, ${r.offset}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRotor({ name, values, turnovers }: RotorValues) {
|
|
||||||
return new Rotor(name, values, turnovers, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComponent<T extends { name: string }>(
|
function getComponent<T extends { name: string }>(
|
||||||
components: T[],
|
components: Record<string, T>,
|
||||||
componentName: string,
|
componentName: string,
|
||||||
): T {
|
): T {
|
||||||
const component = components.find(({ name }) => name === componentName);
|
const component = components[componentName];
|
||||||
if (!component) throw new Error(`unknown reflector: ${component}`);
|
if (!component) throw new Error(`unknown component: ${component}`);
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export class Plugboard {
|
|||||||
this.links.set(b, a);
|
this.links.set(b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlink(a: string) {
|
unstecker(a: string) {
|
||||||
checkCharacter(a, "a");
|
checkCharacter(a, "a");
|
||||||
|
|
||||||
if (!this.links.has(a)) return;
|
if (!this.links.has(a)) return;
|
||||||
|
@ -27,12 +27,12 @@ export class Rotor {
|
|||||||
throw new Error("offset must be an integer");
|
throw new Error("offset must be an integer");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char = this.values[this.offset];
|
|
||||||
const turnover = this.turnovers.includes(char);
|
|
||||||
|
|
||||||
this.offset += times;
|
this.offset += times;
|
||||||
this.offset %= 26;
|
this.offset %= 26;
|
||||||
|
|
||||||
|
const char = String.fromCharCode("A".charCodeAt(0) + this.offset);
|
||||||
|
const turnover = this.turnovers.includes(char);
|
||||||
|
|
||||||
return turnover;
|
return turnover;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,16 +45,17 @@ export class Rotor {
|
|||||||
|
|
||||||
const posNum = pos.charCodeAt(0);
|
const posNum = pos.charCodeAt(0);
|
||||||
const input = (posNum - "A".charCodeAt(0) + this.offset) % 26;
|
const input = (posNum - "A".charCodeAt(0) + this.offset) % 26;
|
||||||
|
|
||||||
return this.values[input];
|
return this.values[input];
|
||||||
}
|
}
|
||||||
|
|
||||||
out(pos: string): string {
|
out(pos: string): string {
|
||||||
checkCharacter(pos, "position");
|
checkCharacter(pos, "position");
|
||||||
|
|
||||||
const i = this.values.findIndex((char) => char === pos);
|
let i = this.values.findIndex((char) => char === pos);
|
||||||
const str = String.fromCharCode(
|
i -= this.offset;
|
||||||
"A".charCodeAt(0) + ((i + this.offset) % 26),
|
if (i < 0) i = 26 + i;
|
||||||
);
|
const str = String.fromCharCode("A".charCodeAt(0) + i);
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user