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";
|
||||
|
||||
const machine = new Machine("A", "I", "II", "III");
|
||||
let machine = new Machine("A", "I", "II", "III");
|
||||
|
||||
console.log(machine.input("THISISAMESSAGEFORYOU"));
|
||||
console.log("rings", machine.rings());
|
||||
console.log("THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOG");
|
||||
|
||||
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 {
|
||||
buildReflectors,
|
||||
buildRotors,
|
||||
ReflectorValues,
|
||||
RotorValues,
|
||||
} from "./loadComponentData";
|
||||
M3ReflectorName,
|
||||
m3Reflectors,
|
||||
M3RotorName,
|
||||
m3Rotots as m3Rotors,
|
||||
M4ReflectorName,
|
||||
m4Reflectors,
|
||||
M4RotorName,
|
||||
m4Rotors,
|
||||
} from "./components";
|
||||
import { Plugboard } from "./plugboard";
|
||||
import { Rotor } from "./rotors";
|
||||
import { checkCharacter } from "./validation";
|
||||
|
||||
const rotors = buildRotors("rotors.csv");
|
||||
const reflectors = buildReflectors("reflectors.csv");
|
||||
|
||||
export class Machine {
|
||||
plugboard: Plugboard;
|
||||
reflector: ReflectorValues;
|
||||
reflector: Map<string, string>;
|
||||
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.reflector = getComponent(reflectors, reflector);
|
||||
const reflectorComponent = getComponent(
|
||||
{ ...m3Reflectors, ...m4Reflectors },
|
||||
reflector,
|
||||
);
|
||||
|
||||
this.rotors = [];
|
||||
this.rotors.push(getRotor(getComponent(rotors, first)));
|
||||
this.rotors.push(getRotor(getComponent(rotors, second)));
|
||||
this.rotors.push(getRotor(getComponent(rotors, third)));
|
||||
this.reflector = new Map();
|
||||
for (let i = 0; i < 26; i++) {
|
||||
const aCharCode = "A".charCodeAt(0);
|
||||
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[] {
|
||||
@ -40,20 +78,42 @@ export class Machine {
|
||||
checkCharacter(char, "character");
|
||||
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
|
||||
let scrambled = this.plugboard.in(char);
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
// reflector
|
||||
scrambled = this.reflector.reflector.get(scrambled)!;
|
||||
scrambled = this.reflector.get(scrambled)!;
|
||||
|
||||
// rotors, second pass (out)
|
||||
for (let i = this.rotors.length - 1; i >= 0; i--) {
|
||||
@ -63,22 +123,23 @@ export class Machine {
|
||||
|
||||
// plugboard, second pass
|
||||
output += this.plugboard.in(scrambled);
|
||||
|
||||
let state = "";
|
||||
for (const r of this.rotors) {
|
||||
state += ` ${r.name}, ${r.offset}`;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
function getRotor({ name, values, turnovers }: RotorValues) {
|
||||
return new Rotor(name, values, turnovers, 0);
|
||||
}
|
||||
|
||||
function getComponent<T extends { name: string }>(
|
||||
components: T[],
|
||||
components: Record<string, T>,
|
||||
componentName: string,
|
||||
): T {
|
||||
const component = components.find(({ name }) => name === componentName);
|
||||
if (!component) throw new Error(`unknown reflector: ${component}`);
|
||||
const component = components[componentName];
|
||||
if (!component) throw new Error(`unknown component: ${component}`);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class Plugboard {
|
||||
this.links.set(b, a);
|
||||
}
|
||||
|
||||
unlink(a: string) {
|
||||
unstecker(a: string) {
|
||||
checkCharacter(a, "a");
|
||||
|
||||
if (!this.links.has(a)) return;
|
||||
|
@ -27,12 +27,12 @@ export class Rotor {
|
||||
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 %= 26;
|
||||
|
||||
const char = String.fromCharCode("A".charCodeAt(0) + this.offset);
|
||||
const turnover = this.turnovers.includes(char);
|
||||
|
||||
return turnover;
|
||||
}
|
||||
|
||||
@ -45,16 +45,17 @@ export class Rotor {
|
||||
|
||||
const posNum = pos.charCodeAt(0);
|
||||
const input = (posNum - "A".charCodeAt(0) + this.offset) % 26;
|
||||
|
||||
return this.values[input];
|
||||
}
|
||||
|
||||
out(pos: string): string {
|
||||
checkCharacter(pos, "position");
|
||||
|
||||
const i = this.values.findIndex((char) => char === pos);
|
||||
const str = String.fromCharCode(
|
||||
"A".charCodeAt(0) + ((i + this.offset) % 26),
|
||||
);
|
||||
let i = this.values.findIndex((char) => char === pos);
|
||||
i -= this.offset;
|
||||
if (i < 0) i = 26 + i;
|
||||
const str = String.fromCharCode("A".charCodeAt(0) + i);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
Reference in New Issue
Block a user