From dfc8f517f2746fe4469552e6ccd84c90f88e198c Mon Sep 17 00:00:00 2001 From: VirtualColossus Date: Wed, 27 Nov 2019 12:48:09 +0000 Subject: [PATCH] Added Colossus operation --- src/core/lib/Colossus.mjs | 426 +++++++++++++++++++++++++ src/core/lib/Lorenz.mjs | 155 +++++++++ src/core/operations/Colossus.mjs | 517 +++++++++++++++++++++++++++++++ 3 files changed, 1098 insertions(+) create mode 100644 src/core/lib/Colossus.mjs create mode 100644 src/core/lib/Lorenz.mjs create mode 100644 src/core/operations/Colossus.mjs diff --git a/src/core/lib/Colossus.mjs b/src/core/lib/Colossus.mjs new file mode 100644 index 00000000..d25d3285 --- /dev/null +++ b/src/core/lib/Colossus.mjs @@ -0,0 +1,426 @@ +/** + * Colossus - an emulation of the world's first electronic computer + * + * @author VirtualColossus + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import {INIT_PATTERNS, ITA2_TABLE, ROTOR_SIZES} from "../lib/Lorenz.mjs"; + +/** + * Colossus simulator class. + */ +export class ColossusComputer { + /** + * Construct a Colossus. + * + * @param {string} ciphertext + * @param {string} pattern - named pattern of Chi, Mu and Psi wheels + * @param {Object} qbusin - which data inputs are being sent to q bus - each can be null, plain or delta + * @param {Object[]} qbusswitches - Q bus calculation switches, multiple rows + * @param {Object} control - control switches which specify stepping modes + * @param {Object} starts - rotor start positions + */ + constructor(ciphertext, pattern, qbusin, qbusswitches, control, starts, settotal, limit) { + + this.ITAlookup = ITA2_TABLE; + this.REVERSE_ITAlookup = {}; + for (const letter in this.ITAlookup) { + const code = this.ITAlookup[letter]; + this.REVERSE_ITAlookup[code] = letter; + } + + this.initThyratrons(pattern); + + this.ciphertext = ciphertext; + this.qbusin = qbusin; + this.qbusswitches = qbusswitches; + this.control = control; + this.starts = starts; + this.settotal = settotal; + this.limitations = limit; + + + this.allCounters = [0, 0, 0, 0, 0]; + + this.Zbits = [0, 0, 0, 0, 0]; // Z input is the cipher tape + this.ZbitsOneBack = [0, 0, 0, 0, 0]; // for delta + this.Qbits = [0, 0, 0, 0, 0]; // input generated for placing onto the Q-bus (the logic processor) + + this.Xbits = [0, 0, 0, 0, 0]; // X is the Chi wheel bits + this.Xptr = [0, 0, 0, 0, 0]; // pointers to the current X bits (Chi wheels) + this.XbitsOneBack = [0, 0, 0, 0, 0]; // the X bits one back (for delta) + + this.Sbits = [0, 0, 0, 0, 0]; // S is the Psi wheel bits + this.Sptr = [0, 0, 0, 0, 0]; // pointers to the current S bits (Psi wheels) + this.SbitsOneBack = [0, 0, 0, 0, 0]; // the S bits one back (for delta) + + this.Mptr = [0, 0]; + + this.rotorPtrs = {}; + + } + + /** + * Begin a run + * @returns {object} - + */ + run() { + + let result = { + printout: "" + }; + + // loop until our start positions are back to the beginning + this.rotorPtrs = {X1: this.starts.X1, X2: this.starts.X2, X3: this.starts.X3, X4: this.starts.X4, X5: this.starts.X5, M61: this.starts.M61, M37: this.starts.M37, S1: this.starts.S1, S2: this.starts.S2, S3: this.starts.S3, S4: this.starts.S4, S5: this.starts.S5}; + //this.rotorPtrs = this.starts; + let runcount = 1; + + const fast = this.control.fast; + const slow = this.control.slow; + + // Print Headers + result.printout += fast + " " + slow + "\n"; + + do { + + this.allCounters = [0, 0, 0, 0, 0]; + this.ZbitsOneBack = [0, 0, 0, 0, 0]; + this.XbitsOneBack = [0, 0, 0, 0, 0]; + + // Run full tape loop and process counters + this.runTape(); + + // Only print result if larger than set total + var fastRef = "00"; + var slowRef = "00"; + if (fast !== "") fastRef = this.leftPad(this.rotorPtrs[fast],2); + if (slow !== "") slowRef = this.leftPad(this.rotorPtrs[slow],2); + result.printout += fastRef + " " + slowRef + " : "; + for (let c=0;c<5;c++) { + if (this.allCounters[c] > this.settotal) { + result.printout += String.fromCharCode(c+97) + this.allCounters[c]+" "; + } + } + result.printout += "\n"; + + // Step fast rotor if required + if (fast != '') { + this.rotorPtrs[fast]++; + if (this.rotorPtrs[fast] > ROTOR_SIZES[fast]) this.rotorPtrs[fast] = 1; + } + + // Step slow rotor if fast rotor has returned to initial start position + if (slow != '' && this.rotorPtrs[fast] === this.starts[fast]) { + this.rotorPtrs[slow]++; + if (this.rotorPtrs[slow] > ROTOR_SIZES[slow]) this.rotorPtrs[slow] = 1; + } + + runcount++; + + } while (JSON.stringify(this.rotorPtrs) !== JSON.stringify(this.starts)); + + result.counters = this.allCounters; + result.runcount = runcount; + + return result; + }; + + /** + * Run tape loop + */ + runTape() { + + let charZin = ""; + + this.Xptr = [this.rotorPtrs.X1, this.rotorPtrs.X2, this.rotorPtrs.X3, this.rotorPtrs.X4, this.rotorPtrs.X5]; + this.Mptr = [this.rotorPtrs.M37, this.rotorPtrs.M61]; + this.Sptr = [this.rotorPtrs.S1, this.rotorPtrs.S2, this.rotorPtrs.S3, this.rotorPtrs.S4, this.rotorPtrs.S5]; + //console.log(this.Xptr); + + // Run full loop of all character on the input tape (Z) + for (let i=0; i ROTOR_SIZES["S"+(r+1)]) this.Sptr[r] = 1; + } + } + + // Move M37 rotor if M61 set + if (this.rings.M[1][this.Mptr[1]-1]==1) this.Mptr[0]++; + if (this.Mptr[0] > ROTOR_SIZES.M37) this.Mptr[0]=1; + + // Always move M61 rotor + this.Mptr[1]++; + if (this.Mptr[1] > ROTOR_SIZES.M61) this.Mptr[1]=1; + + } + + /** + * Get Q bus inputs + */ + getQbusInputs(charZin) { + // Zbits - the bits from the current character from the cipher tape. + this.Zbits = this.ITAlookup[charZin].split(""); + //console.log('Zbits = '+this.Zbits); + if (this.qbusin.Z == 'Z') { + // direct Z + this.Qbits = this.Zbits; + //console.log('direct Z: Qbits = '+this.Qbits); + } else if(this.qbusin.Z == 'ΔZ') { + // delta Z, the Bitwise XOR of this character Zbits + last character Zbits + for(let b=0;b<5;b++) { + this.Qbits[b] = this.Zbits[b] ^ this.ZbitsOneBack[b]; + } + + //console.log('delta Z: Qbits = '+this.Qbits); + } + this.ZbitsOneBack = this.Zbits.slice(); // copy value of object, not reference + + //console.log('Zin::Q bus now '+this.Qbits+'['+this.REVERSE_ITAlookup[this.Qbits.join("")]+']'); + + // Xbits - the current Chi wheel bits + //console.log(this.rings.X); + //console.log('Xptr = '+this.Xptr); + + for (let b=0;b<5;b++) { + this.Xbits[b] = this.rings.X[b+1][this.Xptr[b]-1]; + } + if (this.qbusin.Chi != "") { + //console.log('X Bits '+this.Xbits+'['+this.REVERSE_ITAlookup[this.Xbits.join("")]+']'); + //console.log('X Char = ' + this.REVERSE_ITAlookup[this.Xbits.join("")]); + if (this.qbusin.Chi == "Χ") { + // direct X added to Qbits + for (let b=0;b<5;b++) { + this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b]; + } + //console.log('direct X: Qbits = '+this.Qbits); + } else if(this.qbusin.Chi == "ΔΧ") { + // delta X + for(let b=0;b<5;b++) { + this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b]; + this.Qbits[b] = this.Qbits[b] ^ this.XbitsOneBack[b]; + } + //console.log('delta X: Xbits = '+this.Xbits+' Xbits-1 = '+this.XbitsOneBack); + //console.log('delta X: Qbits = '+this.Qbits); + } + } + this.XbitsOneBack = this.Xbits.slice(); + //console.log('setting XbitsOneBack to '+this.Xbits); + + //console.log('Zin+Xin::Q bus now '+this.Qbits+'['+this.REVERSE_ITAlookup[this.Qbits.join("")]+']'); + + // Sbits - the current Psi wheel bits + //console.log(this.rings.S); + //console.log('Sptr = '+this.Sptr); + for (let b=0;b<5;b++) { + this.Sbits[b] = this.rings.S[b+1][this.Sptr[b]-1]; + } + if (this.qbusin.Psi != "") { + //console.log('S Bits '+this.Sbits+'['+this.REVERSE_ITAlookup[this.Sbits.join("")]+']'); + //console.log('S Char = ' + this.REVERSE_ITAlookup[this.Sbits.join("")]); + if(this.qbusin.Psi == "Ψ") { + // direct S added to Qbits + for (let b=0;b<5;b++) { + this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b]; + } + //console.log('direct S: Qbits = '+this.Qbits); + } else if(this.qbusin.Psi == "ΔΨ") { + // delta S + for (let b=0;b<5;b++) { + this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b]; + this.Qbits[b] = this.Qbits[b] ^ this.SbitsOneBack[b]; + } + //console.log('delta S: Qbits = '+this.Qbits); + } + } + this.SbitsOneBack = this.Sbits.slice(); + + //console.log('Q bus now '+this.Qbits+'['+this.REVERSE_ITAlookup[this.Qbits.join("")]+']'); + } + + /** + * Conditional impulse Q bus section + */ + runQbusProcessingConditional() { + let cnt = [-1, -1, -1, -1, -1]; + const numrows = this.qbusswitches.condition.length; + + for (let r=0;r= 0 && Qswitch[s] != this.Qbits[s]) result = false; + } + // Check for NOT switch + if (row.Negate) result = !result; + + // AND each row to get final result + if (cnt[cPnt] == -1) { + cnt[cPnt] = result; + } else if (result==0) { + cnt[cPnt] = 0; + } + } + } + + // Negate the whole column, this allows A OR B by doing NOT(NOT A AND NOT B) + for (let c=0;c<5;c++) { + if (this.qbusswitches.condNegateAll) cnt[c] = !cnt[c]; + + if (cnt[c]==1) this.allCounters[c]++; + } + + } + + /** + * Addition of impulses Q bus section + */ + runQbusProcessingAddition() { + let row = this.qbusswitches.addition[0]; + const Qswitch = row.Qswitches.slice(); + if (row.C1) { + let addition = 0; + for (let s=0;s<5;s++) { + // XOR addition + if (Qswitch[s]) { + addition = addition ^ this.Qbits[s]; + } + } + const equals = (row.Equals==""?-1:(row.Equals=="."?0:1)); + if (addition == equals) { + this.allCounters[0]++; + } + } + //console.log("counter1="+this.allCounters[0]); + } + + /** + * Initialise thyratron rings + * These hold the pattern of 1s & 0s for each rotor on banks of thyraton GT1C valves which act as a one-bit store. + */ + initThyratrons(pattern) { + this.rings = { + X: { + 1: INIT_PATTERNS[pattern].X[1].slice().reverse(), + 2: INIT_PATTERNS[pattern].X[2].slice().reverse(), + 3: INIT_PATTERNS[pattern].X[3].slice().reverse(), + 4: INIT_PATTERNS[pattern].X[4].slice().reverse(), + 5: INIT_PATTERNS[pattern].X[5].slice().reverse() + }, + M: { + 1: INIT_PATTERNS[pattern].M[1].slice().reverse(), + 2: INIT_PATTERNS[pattern].M[2].slice().reverse(), + }, + S: { + 1: INIT_PATTERNS[pattern].S[1].slice().reverse(), + 2: INIT_PATTERNS[pattern].S[2].slice().reverse(), + 3: INIT_PATTERNS[pattern].S[3].slice().reverse(), + 4: INIT_PATTERNS[pattern].S[4].slice().reverse(), + 5: INIT_PATTERNS[pattern].S[5].slice().reverse() + } + }; + } + + /** + * Left Pad number + */ + leftPad(number, targetLength) { + let output = number + ""; + while (output.length < targetLength) { + output = "0" + output; + } + return output; + } + + /** + * Read argument bus switches X & . and convert to 1 & 0 + */ + readBusSwitches(row) { + let output = [-1, -1, -1, -1, -1]; + for (let c=0;c<5;c++) { + if (row[c]===".") output[c] = 0; + if (row[c]==="x") output[c] = 1; + } + return output; + } + +} diff --git a/src/core/lib/Lorenz.mjs b/src/core/lib/Lorenz.mjs new file mode 100644 index 00000000..e69946fa --- /dev/null +++ b/src/core/lib/Lorenz.mjs @@ -0,0 +1,155 @@ +/** + * Resources required by the Lorenz SZ40/42 and Colossus + * + * @author VirtualColossus + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import OperationError from "../errors/OperationError.mjs"; + +export const SWITCHES = [ + {name: "Up (.)", value: "."}, + {name: "Centre", value: ""}, + {name: "Down (x)", value: "x"} +]; + +export const ITA2_TABLE = { + "A": "11000", + "B": "10011", + "C": "01110", + "D": "10010", + "E": "10000", + "F": "10110", + "G": "01011", + "H": "00101", + "I": "01100", + "J": "11010", + "K": "11110", + "L": "01001", + "M": "00111", + "N": "00110", + "O": "00011", + "P": "01101", + "Q": "11101", + "R": "01010", + "S": "10100", + "T": "00001", + "U": "11100", + "V": "01111", + "W": "11001", + "X": "10111", + "Y": "10101", + "Z": "10001", + "3": "00010", + "4": "01000", + "9": "00100", + "/": "00000", + " ": "00100", + ".": "00100", + "8": "11111", + "5": "11011", + "-": "11111", + "+": "11011" +}; + +export const ROTOR_SIZES = { + S1: 43, + S2: 47, + S3: 51, + S4: 53, + S5: 59, + M37: 37, + M61: 61, + X1: 41, + X2: 31, + X3: 29, + X4: 26, + X5: 23 +}; + +/** + * Initial rotor patterns + */ +export const INIT_PATTERNS = { + "No Pattern": { + "X": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "S": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 3: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 4: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 5: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "M": { + 1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 2: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } + + }, + "KH Pattern": { + "X": { + 1: [0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], + 2: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0], + 3: [0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0], + 4: [1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0], + 5: [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0] + }, + "S": { + 1: [0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1], + 2: [0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1], + 3: [0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1], + 4: [0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], + 5: [1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0] + }, + "M": { + 1: [0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0], + 2: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0] + } + }, + "ZMUG Pattern": { + "X": { + 1: [0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0], + 2: [1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0], + 3: [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0], + 4: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1], + 5: [0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1] + }, + "S": { + 1: [1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0], + 2: [0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1], + 3: [0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1], + 4: [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1], + 5: [1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0] + }, + "M": { + 1: [1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1], + 2: [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1] + } + }, + "BREAM Pattern": { + "X": { + 1: [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0], + 2: [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1], + 3: [1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + 4: [1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0], + 5: [0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0] + }, + "S": { + 1: [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], + 2: [1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], + 3: [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + 4: [0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1], + 5: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] + }, + "M": { + 1: [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1], + 2: [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1] + } + } +}; \ No newline at end of file diff --git a/src/core/operations/Colossus.mjs b/src/core/operations/Colossus.mjs new file mode 100644 index 00000000..09160f38 --- /dev/null +++ b/src/core/operations/Colossus.mjs @@ -0,0 +1,517 @@ +/** + * Emulation of Colossus. + * + * @author VirtualColossus [martin@virtualcolossus.co.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError.mjs"; +import { ColossusComputer } from "../lib/Colossus.mjs"; +import { SWITCHES } from "../lib/Lorenz.mjs"; + +/** + * Colossus operation + */ +class Colossus extends Operation { + + /** + * Lorenz constructor + */ + constructor() { + super(); + this.name = "Colossus"; + this.module = "Bletchley"; + this.description = "Colossus ... "; + this.infoURL = "https://wikipedia.org/wiki/Colossus_computer"; + this.inputType = "string"; + this.outputType = "JSON"; + this.presentType = "html"; + this.args = [ + { + name: "Input", + type: "label" + }, + { + name: "Pattern", + type: "option", + value: ["KH Pattern", "ZMUG Pattern", "BREAM Pattern"] + }, + { + name: "QBusZ", + type: "option", + value: ["", "Z", "ΔZ"] + }, + { + name: "QBusΧ", + type: "option", + value: ["", "Χ", "ΔΧ"] + }, + { + name: "QBusΨ", + type: "option", + value: ["", "Ψ", "ΔΨ"] + }, + { + name: "Limitation", + type: "option", + value: ["None", "Χ2", "Χ2 + P5", "X2 + Ψ1", "X2 + Ψ1 + P5"] + }, + { + name: "K Rack Option", + type: "argSelector", + "value": [ + { + name: "Select Program", + on: [7], + off: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] + }, + { + name: "Top Section - Conditional", + on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], + off: [7, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] + }, + { + name: "Bottom Section - Addition", + on: [31, 32, 33, 34, 35, 36, 37, 38, 39, 40], + off: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] + }, + { + name: "Advanced", + on: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40], + off: [7] + } + ] + }, + { + name: "Program to run", + type: "option", + value: ["", "1+2=. (1+2 Break In, Find X1,X2)", "4=3=/1=2 (Given X1,X2 find X4,X5)", "/,5,U (Count chars to find X3)"] + }, + { + name: "K Rack: Conditional", + type: "label" + }, + { + name: "R1-Q1", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q2", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q3", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q4", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Q5", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R1-Negate", + type: "boolean" + }, + { + name: "R1-Counter", + type: "option", + value: ["", "1", "2", "3", "4", "5"] + }, + { + name: "R2-Q1", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q2", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q3", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q4", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Q5", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R2-Negate", + type: "boolean" + }, + { + name: "R2-Counter", + type: "option", + value: ["", "1", "2", "3", "4", "5"] + }, + { + name: "R3-Q1", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q2", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q3", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q4", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Q5", + type: "editableOptionShort", + value: SWITCHES, + defaultIndex: 1 + }, + { + name: "R3-Negate", + type: "boolean" + }, + { + name: "R3-Counter", + type: "option", + value: ["", "1", "2", "3", "4", "5"] + }, + { + name: "Negate All", + type: "boolean" + }, + { + name: "K Rack: Addition", + type: "label" + }, + { + name: "Add-Q1", + type: "boolean" + }, + { + name: "Add-Q2", + type: "boolean" + }, + { + name: "Add-Q3", + type: "boolean" + }, + { + name: "Add-Q4", + type: "boolean" + }, + { + name: "Add-Q5", + type: "boolean" + }, + { + name: "Add-Equals", + type: "editableOptionShort", + value: SWITCHES + }, + { + name: "Add-Counter1", + type: "boolean" + }, + { + name: "Add Negate All", + type: "boolean" + }, + { + name: "Total Motor", + type: "boolean" + }, + { + name: "Master Control Panel", + type: "label" + }, + { + name: "Set Total", + type: "number", + value: 0 + }, + { + name: "Fast Step", + type: "option", + value: ["", "X1", "X2", "X3", "X4", "X5", "M37", "M61", "S1", "S2", "S3", "S4", "S5"] + }, + { + name: "Slow Step", + type: "option", + value: ["", "X1", "X2", "X3", "X4", "X5", "M37", "M61", "S1", "S2", "S3", "S4", "S5"] + }, + { + name: "Start X1", + type: "number", + value: 1 + }, + { + name: "Start X2", + type: "number", + value: 1 + }, + { + name: "Start X3", + type: "number", + value: 1 + }, + { + name: "Start X4", + type: "number", + value: 1 + }, + { + name: "Start X5", + type: "number", + value: 1 + }, + { + name: "Start M61", + type: "number", + value: 1 + }, + { + name: "Start M37", + type: "number", + value: 1 + }, + { + name: "Start S1", + type: "number", + value: 1 + }, + { + name: "Start S2", + type: "number", + value: 1 + }, + { + name: "Start S3", + type: "number", + value: 1 + }, + { + name: "Start S4", + type: "number", + value: 1 + }, + { + name: "Start S5", + type: "number", + value: 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {Object} + */ + run(input, args) { + + const pattern = args[1]; + const qbusin = { + "Z": args[2], + "Chi": args[3], + "Psi": args[4], + }; + + const limitation = args[5]; + let lm = [false,false,false]; + if (limitation.includes("Χ2")) lm[0] = true; + if (limitation.includes("Ψ1")) lm[1] = true; + if (limitation.includes("P5")) lm[2] = true; + const limit = { + X2: lm[0], S1: lm[1], P5: lm[2] + }; + + const KRackOpt = args[6]; + const setProgram = args[7]; + + if (KRackOpt === "Select Program" && setProgram !== "") { + args = this.selectProgram(setProgram, args); + } + + // Q1,Q2,Q3,Q4,Q5,negate,counter1 + const qbusswitches = { + condition: [ + {Qswitches: [args[9], args[10], args[11], args[12], args[13]], Negate: args[14], Counter: args[15]}, + {Qswitches: [args[16], args[17], args[18], args[19], args[20]], Negate: args[21], Counter: args[22]}, + {Qswitches: [args[23], args[24], args[25], args[26], args[27]], Negate: args[28], Counter: args[29]} + ], + condNegateAll: args[30], + addition: [ + {Qswitches: [args[32], args[33], args[34], args[35], args[36]], Equals: args[37], C1: args[38]} + ], + addNegateAll: args[39], + totalMotor: args[40] + }; + + const settotal = args[42]; + + // null|fast|slow for each of S1-5,M1-2,X1-5 + const control = { + fast: args[43], + slow: args[44] + }; + + // Start positions + const starts = { + X1: args[45], X2: args[46], X3: args[47], X4: args[48], X5: args[49], + M61: args[50], M37: args[51], + S1: args[52], S2: args[53], S3: args[54], S4: args[55], S5: args[56] + }; + + const colossus = new ColossusComputer(input, pattern, qbusin, qbusswitches, control, starts, settotal, limit); + const result = colossus.run(); + + console.log(result); + + return result; + + } + + /** + * Select Program + */ + selectProgram(progname, args) { + + // Bill Tutte's 1+2 Break In + if(progname == "1+2=. (1+2 Break In, Find X1,X2)") { + // Clear any other counters + args[15] = ""; // Conditional R1 + args[22] = ""; // Conditional R2 + args[29] = ""; // Conditional R3 + // Set Add Q1+Q2=. into Counter 1 + args[32] = true; + args[33] = true; + args[34] = false; + args[35] = false; + args[36] = false; + args[37] = "."; + args[38] = true; + } + + // 4=3=/1=2 : Find X4 & X5 where X1 & X2 are known + if(progname == "4=3=/1=2 (Given X1,X2 find X4,X5)") { + // Set Conditional R1 : Match NOT ..?.. into counter 1 + args[9] = "."; + args[10] = "."; + args[11] = ""; + args[12] = "."; + args[13] = "."; + args[14] = true; + args[15] = "1"; + // Set Conditional R2 : AND Match NOT xx?xx into counter 1 + args[16] = "x"; + args[17] = "x"; + args[18] = ""; + args[19] = "x"; + args[20] = "x"; + args[21] = true; + args[22] = "1"; + // clear Conditional R3 + args[29] = ""; + // Negate result, giving NOT(NOT Q1 AND NOT Q2) which is equivalent to Q1 OR Q2 + args[30] = true; + // Clear Addition row counter + args[38] = false; + } + + // /,5,U : Count number of matches of /, 5 & U to find X3 + if(progname == "/,5,U (Count chars to find X3)") { + // Set Conditional R1 : Match / char, ITA2 = ..... into counter 1 + args[9] = "."; + args[10] = "."; + args[11] = "."; + args[12] = "."; + args[13] = "."; + args[14] = false; + args[15] = "1"; + // Set Conditional R2 : Match 5 char, ITA2 = xx.xx into counter 2 + args[16] = "x"; + args[17] = "x"; + args[18] = "."; + args[19] = "x"; + args[20] = "x"; + args[21] = false; + args[22] = "2"; + // Set Conditional R3 : Match U char, ITA2 = xxx.. into counter 3 + args[23] = "x"; + args[24] = "x"; + args[25] = "x"; + args[26] = "."; + args[27] = "."; + args[28] = false; + args[29] = "3"; + // Clear Negate result + args[30] = false; + // Clear Addition row counter + args[38] = false; + } + + return args; + } + + /** + * Displays Colossus results in an HTML table + * + * @param {Object} output + * @param {Object[]} output.counters + * @returns {html} + */ + present(output) { + console.log("output="+ typeof(output)); + console.log("counters="+ typeof(output.counters)); + + let html = "Colossus Printer\n\n"; + html += output.printout + "\n\n"; + html += "Colossus Counters\n\n"; + html += "\n"; + html += ""; + for (const ct of output.counters) { + html += `\n`; + } + html += ""; + html += "
C1 C2 C3 C4 C5
${ct}
"; + return html; + } + +} + +export default Colossus;