@ondoher/enigma 1.0.13 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -440,7 +440,7 @@ let enigma = new Enigma("I", {reflector: 'B'});
440
440
 
441
441
  enigma.configure({
442
442
  rotors: ['III', 'VI', 'VIII'],
443
- ringSetting: [1, 8, 13],
443
+ ringSettings: [1, 8, 13],
444
444
  plugs: 'AN EZ HK IJ LR MQ OT PV SW UX'
445
445
  });
446
446
 
package/bin/config.js ADDED
@@ -0,0 +1,11 @@
1
+
2
+ /** @type {EnigmaConfig} */
3
+ export const CONFIG = {
4
+ enigma: {
5
+ name: "I",
6
+ reflector: 'B',
7
+ rotors: ['III', 'VI', 'VIII'],
8
+ ringSettings: [1, 8, 13],
9
+ plugs: 'AN EZ HK IJ LR MQ OT PV SW UX'
10
+ }
11
+ }
package/bin/enigma.js CHANGED
@@ -0,0 +1,125 @@
1
+ import parseArgs from 'minimist';
2
+ import { getOptions } from '../lib/utils/options.js';
3
+ import { access, constants, readFile, writeFile } from 'node:fs/promises';
4
+ import { CONFIG } from './config.js';
5
+ import Enigma from 'enigma/Enigma';
6
+
7
+ let argv = parseArgs(process.argv.slice(2));
8
+ let params = argv._;
9
+ let instructionText = '';
10
+
11
+ /** @type {Options} */
12
+ let defaultOptions = {
13
+ file: './enigma.json',
14
+ overwrite: false,
15
+ events: '',
16
+ step: false
17
+ }
18
+
19
+ /** @type {OptionsNameMap} */
20
+ let nameMap = {
21
+ file: 'f',
22
+ overwrite: 'o',
23
+ events: 'e',
24
+ step: 's'
25
+ }
26
+
27
+
28
+ /** @type {Options} */
29
+ let options = getOptions(defaultOptions, argv, nameMap);
30
+ let command = params[0];
31
+
32
+ /**
33
+ * Call this function to check if a file exists
34
+ *
35
+ * @param {String} name
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ async function fileExists(name) {
39
+ try {
40
+ await access(name, constants.R_OK | constants.W_OK);
41
+ return true;
42
+ } catch (e) {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Call this method to see if the config file exists
49
+ * @returns
50
+ */
51
+ async function configExists() {
52
+ return await fileExists(options.file)
53
+ }
54
+
55
+ /**
56
+ *
57
+ * @returns {Promise<EnigmaConfig>}
58
+ */
59
+ async function readConfig() {
60
+ if (!configExists) {
61
+ return CONFIG;
62
+ }
63
+
64
+ return JSON.parse(await readFile( options.file, 'utf-8'));
65
+
66
+ }
67
+
68
+ function instructions() {
69
+ console.log(instructionText);
70
+ }
71
+
72
+ /**
73
+ *
74
+ * @param {string} str
75
+ */
76
+ function error(str) {
77
+ console.log(str);
78
+ }
79
+
80
+ async function init() {
81
+ if (await configExists() && !options.overwrite) {
82
+ error("cannot overwrite config file")
83
+ instructions();
84
+ return;
85
+ }
86
+
87
+ await writeFile(options.file, JSON.stringify(CONFIG, null, " "), 'utf-8');
88
+ }
89
+
90
+ async function runEnigma() {
91
+ let config = await readConfig();
92
+ if (params.length > 1) {
93
+ config.encode = params[1];
94
+ }
95
+ let enigmaConfig = config.enigma;
96
+ if (!config.encode) {
97
+ error('no string to encode');
98
+ instructions();
99
+ return;
100
+ }
101
+ let { name, rotors, ringSettings, reflector, plugs} = enigmaConfig;
102
+ let enigma = new Enigma(name, {reflector});
103
+ enigma.configure({rotors, ringSettings, plugs})
104
+ let decoded = enigma.translate("", config.encode)
105
+ }
106
+
107
+ async function main() {
108
+ instructionText = await readFile('./instructions.txt', 'utf-8');
109
+ if (!command) {
110
+ instructions();
111
+ return;
112
+ }
113
+
114
+ switch (command) {
115
+ case "init":
116
+ await init();
117
+ break;
118
+
119
+ default:
120
+ instructions();
121
+ break;
122
+ }
123
+ }
124
+
125
+ await main();
@@ -0,0 +1,17 @@
1
+ {
2
+ "enigma": {
3
+ "name": "I",
4
+ "reflector": "B",
5
+ "rotors": [
6
+ "III",
7
+ "VI",
8
+ "VIII"
9
+ ],
10
+ "ringSettings": [
11
+ 1,
12
+ 8,
13
+ 13
14
+ ],
15
+ "plugs": "AN EZ HK IJ LR MQ OT PV SW UX"
16
+ }
17
+ }
@@ -0,0 +1 @@
1
+ silly boy
package/bin/types.d.ts ADDED
@@ -0,0 +1,21 @@
1
+
2
+
3
+ type Options = {
4
+ file?: string;
5
+ overwrite?: boolean;
6
+ events?: string;
7
+ step?: boolean;
8
+ }
9
+
10
+ type EnigmaConfig = {
11
+ enigma: {
12
+ name: string;
13
+ reflector: string;
14
+ rotors: string[],
15
+ ringSettings: number[] | string;
16
+ plugs: string | string[];
17
+ },
18
+ rotor?: string,
19
+ options?: Options,
20
+ encode?: string,
21
+ }
package/jsconfig.json CHANGED
@@ -2,8 +2,11 @@
2
2
  "compilerOptions": {
3
3
  "target": "es2022", // Or a newer version like "es2020"
4
4
  "module": "es2022",
5
- "checkJs": true
6
- },
5
+ "checkJs": true,
6
+ "baseUrl": "./",
7
+ "paths": {
8
+ "*": ["node_modules/@types/*", "*"]
9
+ } },
7
10
  "exclude": [
8
11
  "node_modules"
9
12
  ],
@@ -127,12 +127,35 @@ export default class Enigma extends Encoder {
127
127
  * @public
128
128
  */
129
129
  step() {
130
+ // Only the notches on the first two rotors effect stepping. One rotor's
131
+ // alphabet ring prevents the next rotor from turning unless the first
132
+ // rotor's notch is exposed. When a rotor does step, so does the
133
+ // previous rotor because they are attached by the notch in the
134
+ // previous rotor's ring
135
+
136
+ // precalculate if and why a rotor should step, the first rotor is
137
+ // always engaged
138
+ /** @type {{engaged:boolean, nextEngaged: boolean}[]} */
139
+ let step = []
140
+
141
+ step[0] = {engaged: true, nextEngaged: false};
142
+
143
+ for (let idx = 1; idx < 3; idx++) {
144
+ let engaged = this._rotors[idx - 1].atTurnover();
145
+ let nextEngaged = idx < 2 && this._rotors[idx].atTurnover();
146
+
147
+ step[idx] = {engaged, nextEngaged};
148
+ }
149
+
130
150
  this._rotors.forEach((rotor, idx) => {
131
151
  if (rotor.isFixed()) return;
132
152
 
133
- // This is the double stepping. Only do this for the middle rotor
134
- if (rotor.willTurnover() && idx === 1) {
135
- this.pending[idx] = true
153
+ if (step[idx].engaged || step[idx].nextEngaged) {
154
+ rotor.step();
155
+ }
156
+
157
+ // the double-step is caused by the the next rotor stepping
158
+ if (step[idx].nextEngaged) {
136
159
  /** @type {EventData} */
137
160
  let eventData = {
138
161
  name: rotor.name,
@@ -141,18 +164,9 @@ export default class Enigma extends Encoder {
141
164
  event: "double-step",
142
165
  offset: rotor.offset,
143
166
  }
144
-
145
167
  this.fire('double-step', rotor.name, eventData);
146
- };
147
-
148
- if (this.pending[idx]) {
149
- this.pending[idx] = false;
150
- if (rotor.step()) this.pending[idx + 1] = true;
151
168
  }
152
169
  });
153
-
154
- // The first rotor is always stepping
155
- this.pending[0] = true;
156
170
  }
157
171
 
158
172
  /**
@@ -147,7 +147,7 @@ type StepData = {
147
147
  /** the ending rotor position */
148
148
  stop: number;
149
149
 
150
- /** the locations of the turnover points for this rotor */
150
+ /** true if the rotor has rotated to its turnover location */
151
151
  turnover: boolean;
152
152
  }
153
153
 
@@ -5,6 +5,12 @@ import Encoder from "./Encoder.js";
5
5
  * Create an instance of this class to construct a Rotor object. The Rotor class
6
6
  * encapsulates many of the peculiar behaviors of the Enigma. All connector
7
7
  * values here are specified in physical space.
8
+ *
9
+ * There are three positioning systems to keep in mind. There is the logical
10
+ * position that map an input to an output as defined by the wiring. There is
11
+ * the physical position of the rotor in space as related to the location of the
12
+ * connectors, the rotation offset. And there is an additional adjustment, the
13
+ * ring setting, that moves the internal wiring against the rotational position.
8
14
  */
9
15
  export default class Rotor extends Encoder {
10
16
  /**
@@ -18,6 +24,7 @@ export default class Rotor extends Encoder {
18
24
  super(name, "Rotor", settings);
19
25
  let { map = STANDARD_ALPHABET, turnovers = '', ringSetting = 0} = settings
20
26
 
27
+ this._settings = structuredClone(settings);
21
28
  this.map = [...map];
22
29
  this.rightMap = this.makeMap(map);
23
30
  this.leftMap = this.makeReverseMap(this.rightMap);
@@ -36,6 +43,17 @@ export default class Rotor extends Encoder {
36
43
  }, turnoverSet);
37
44
  }
38
45
 
46
+ /**
47
+ * @returns {RotorSetup}
48
+ */
49
+ get settings() {
50
+ return this._settings;
51
+ }
52
+
53
+ // get offset() {
54
+ // return this._offset
55
+ // }
56
+
39
57
  /**
40
58
  * Call this method to select the initial rotation of the rotor. This is a
41
59
  * letter offset from the logical 0 connector. The initial rotation will
@@ -83,13 +101,19 @@ export default class Rotor extends Encoder {
83
101
  }
84
102
 
85
103
  /**
86
- * Call this method to step the rotor
104
+ * Call this method to step the rotor. This is only responsible for updating
105
+ * the internal state. Double stepping is done by the enigma class
87
106
  *
88
107
  * @public
89
- *
90
- * @returns {Boolean} true if the next rotor should be stepped
91
108
  */
92
109
  step() {
110
+ // let start = this.offset;
111
+
112
+ // // do the step
113
+ // this.offset = this.normalize(this.offset + 1);
114
+
115
+ // let stop = this.offset;
116
+
93
117
  // Because the turnover notch is attached to the outer ring, and the
94
118
  // logical coordinates are based on the wiring, the logical position of
95
119
  // the notch for turnover needs to be adjusted to remove the ring offset.
@@ -111,26 +135,21 @@ export default class Rotor extends Encoder {
111
135
  turnover, start, stop
112
136
  }
113
137
 
114
- // console.log("firing", eventData);
115
138
  this.fire('step', this.name, eventData);
116
139
 
117
140
  return turnover;
118
141
  }
119
142
 
120
143
  /**
121
- * Call this method to see if the next step on this rotor will lead to
122
- * turnover. The Enigma class will call this on the middle rotor to handle
123
- * double stepping.
144
+ * Call this method to see if the router turnover notch is exposed.
124
145
  *
125
146
  * @public
126
147
  *
127
- * @returns true if this rotor will turnover on the next step
148
+ * @returns true if it is, false otherwise
128
149
  */
129
- willTurnover() {
130
- let turnoverOffset = this.normalize(this.offset + this.ringOffset);
131
-
132
- // double stepping happens when we step to the turnover point
133
- return this._turnovers.has(turnoverOffset);
150
+ atTurnover() {
151
+ let currentOffset = this.normalize(this.offset + this.ringOffset);
152
+ return this._turnovers.has(currentOffset);
134
153
  }
135
154
 
136
155
 
@@ -11,7 +11,7 @@ describe('Rotor Test Cases', function() {
11
11
  map: rotorData.createData.map,
12
12
  });
13
13
 
14
- expect(rotor.leftMap).toEqual(rotorData.createData.reverseMap)
14
+ expect(rotor["leftMap"]).toEqual(rotorData.createData.reverseMap)
15
15
  });
16
16
 
17
17
  it('sets the length to the map length', function() {
@@ -20,7 +20,7 @@ describe('Rotor Test Cases', function() {
20
20
  map: rotorData.createData.map,
21
21
  });
22
22
 
23
- expect(rotor.length).toEqual(rotorData.createData.map.length)
23
+ expect(rotor["length"]).toEqual(rotorData.createData.map.length)
24
24
  });
25
25
 
26
26
  it('remembers multiple turnovers', function() {
@@ -88,7 +88,7 @@ describe('Rotor Test Cases', function() {
88
88
 
89
89
  it('handles double step', function() {
90
90
  rotor.setStartPosition('Q');
91
- let result = rotor.willTurnover();
91
+ let result = rotor.atTurnover();
92
92
  expect(result).toBe(true);
93
93
  });
94
94
 
@@ -115,7 +115,7 @@ describe('Rotor Test Cases', function() {
115
115
  it('handles double step', function() {
116
116
  rotor.ringOffset = 1;
117
117
  rotor.setStartPosition('Q');
118
- let result = rotor.willTurnover();
118
+ let result = rotor.atTurnover();
119
119
  expect(result).toBe(true);
120
120
  });
121
121
 
@@ -0,0 +1,63 @@
1
+ import * as minimist from "minimist";
2
+
3
+ /**
4
+ * Call this method to try to convert the input value to a
5
+ * boolean value, if not the return result will be the value passed in
6
+ *
7
+ * @param {string | number | boolean} val - the value to check
8
+ * @returns {string | boolean | number} the converted boolean or the input value
9
+ */
10
+ function checkBoolean(val) {
11
+ let boolValues = {
12
+ 'true' : true,
13
+ 'false': false,
14
+ 'on': true,
15
+ 'off': false,
16
+ 'set': true,
17
+ 'clear': false,
18
+ }
19
+
20
+ if (val === true) return true;
21
+ if (val === false) return false;
22
+
23
+ return boolValues[val] !== undefined ? boolValues[val] : val;
24
+ }
25
+
26
+ /**
27
+ * Call this method to get the value of an option from the cli. It will convert
28
+ * this value to a boolean if it can.
29
+ *
30
+ * @param {minimist.ParsedArgs} argv - the parsed command line
31
+ * @param {String} name - the full name of the option
32
+ * @param {String} short - the short name of the optio
33
+ * @returns {OptionsValue} the value of the option
34
+ */
35
+ export function getOption(argv, name, short) {
36
+ if (argv[name] !== undefined) return checkBoolean(argv[name]);
37
+ if (argv[short] != undefined) return checkBoolean(argv[short]);
38
+ }
39
+
40
+ /**
41
+ * Call this method to parse all the command line options, those
42
+ * that start with either - or --.
43
+ *
44
+ * @param {OptionValues} defaults - The default values, these will be returned if they are unchanged
45
+ * @param {minimist.ParsedArgs} argv - the parsed command line
46
+ * @param {OptionsNameMap} nameMap - a mapping between the full name and the short name
47
+ * @returns {OptionValues} the parsed options
48
+ */
49
+ export function getOptions(defaults, argv, nameMap) {
50
+ let longNames = Object.keys(nameMap);
51
+ let options = structuredClone(defaults);
52
+
53
+ options = longNames.reduce(function(options, long) {
54
+ let short = nameMap[long];
55
+ let option = getOption(argv, long, short);
56
+
57
+ options[long] = option ?? options[long];
58
+
59
+ return options;
60
+ }, options);
61
+
62
+ return options;
63
+ }
@@ -0,0 +1,5 @@
1
+
2
+ type OptionsValue = string | number | boolean
3
+ type OptionValues = {[name: string]: OptionsValue}
4
+
5
+ type OptionsNameMap = {[name: string]: string}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ondoher/enigma",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Enigma machine simulator with tools and documentation to help you build your own.",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./types/index.d.ts",
@@ -27,12 +27,14 @@
27
27
  "homepage": "https://github.com/Ondoher/enigma#readme",
28
28
  "type": "module",
29
29
  "devDependencies": {
30
- "@types/node": "^25.0.10",
31
30
  "@types/jasmine": "^4.6.6",
32
- "undici-types": "^7.19.0",
31
+ "@types/node": "^25.0.10",
33
32
  "jasmine": "^4.5.0",
34
- "jasmine-console-reporter": "^3.1.0"
33
+ "jasmine-console-reporter": "^3.1.0",
34
+ "undici-types": "^7.19.0"
35
35
  },
36
36
  "dependencies": {
37
+ "@types/minimist": "^1.2.5",
38
+ "minimist": "^1.2.8"
37
39
  }
38
40
  }