diff --git a/src/core/lib/Bombe.mjs b/src/core/lib/Bombe.mjs index 1e6c3d2d..3103f56a 100644 --- a/src/core/lib/Bombe.mjs +++ b/src/core/lib/Bombe.mjs @@ -81,6 +81,104 @@ class Edge { } } +/** + * As all the Bombe's rotors move in step, at any given point the vast majority of the scramblers + * in the machine share the majority of their state, which is hosted in this class. + */ +class SharedScrambler { + /** + * SharedScrambler constructor. + * @param {Object[]} rotors - List of rotors in the shared state _only_. + * @param {Object} reflector - The reflector in use. + */ + constructor(rotors, reflector) { + this.reflector = reflector; + this.rotors = rotors; + this.rotorsRev = [].concat(rotors).reverse(); + this.lowerCache = new Array(26); + this.higherCache = new Array(26); + for (let i=0; i<26; i++) { + this.higherCache[i] = new Array(26); + } + this.cacheGen(); + } + + /** + * Step the rotors forward. + * @param {number} n - How many rotors to step. This includes the rotors which are not part of + * the shared state, so should be 2 or more. + */ + step(n) { + for (let i=0; i=this.rotors.length-n; i--) { - this.rotors[i].step(); - } + step() { + // The Bombe steps the slowest rotor on an actual Enigma fastest, for reasons. + // ...but for optimisation reasons I'm going to cheat and not do that, as this vastly + // simplifies caching the state of the majority of the scramblers. The results are the + // same, just in a slightly different order. + this.rotor.step(); } + /** * Run a letter through the scrambler. * @param {number} i - The letter to transform (as a number) @@ -125,13 +224,14 @@ class Scrambler { */ transform(i) { let letter = i; - for (const rotor of this.rotors) { - letter = rotor.transform(letter); - } - letter = this.reflector.transform(letter); - for (const rotor of this.rotorsRev) { - letter = rotor.revTransform(letter); + const cached = this.baseScrambler.fullTransform(this.rotor.pos, i); + if (cached !== undefined) { + return cached; } + letter = this.rotor.transform(letter); + letter = this.baseScrambler.transform(letter); + letter = this.rotor.revTransform(letter); + this.baseScrambler.addCache(this.rotor.pos, i, letter); return letter; } @@ -155,15 +255,11 @@ class Scrambler { */ getPos() { let result = ""; - for (let i=0; i 1) { + this.sharedScrambler.step(n); + } for (const scrambler of this.allScramblers) { - scrambler.step(n); + scrambler.step(); } // Send status messages at what seems to be a reasonably sensible frequency // (note this won't be triggered on 3-rotor runs - they run fast enough it doesn't seem necessary) diff --git a/tests/operations/tests/Bombe.mjs b/tests/operations/tests/Bombe.mjs index 65f4b701..6a96884c 100644 --- a/tests/operations/tests/Bombe.mjs +++ b/tests/operations/tests/Bombe.mjs @@ -27,6 +27,7 @@ TestRegister.addTests([ ] }, { + // This test produces a menu that doesn't use the first letter, which is also a good test name: "Bombe: 3 rotor (other stecker)", input: "JBYALIHDYNUAAVKBYM", expectedMatch: /LGA \(plugboard: AG\): QFIMUMAFKMQSKMYNGW/, @@ -80,8 +81,7 @@ TestRegister.addTests([ } ] }, - /* - * Long test is long + // This test is a bit slow - it takes about 12s on my test hardware { name: "Bombe: 4 rotor", input: "LUOXGJSHGEDSRDOQQX", @@ -100,7 +100,6 @@ TestRegister.addTests([ } ] }, - */ { name: "Bombe: no crib", input: "JBYALIHDYNUAAVKBYM",