Colossus.mjs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /**
  2. * Colossus - an emulation of the world's first electronic computer
  3. *
  4. * @author VirtualColossus
  5. * @copyright Crown Copyright 2019
  6. * @license Apache-2.0
  7. */
  8. import OperationError from "../errors/OperationError.mjs";
  9. import Utils from "../Utils.mjs";
  10. import {INIT_PATTERNS, ITA2_TABLE, ROTOR_SIZES} from "../lib/Lorenz.mjs";
  11. /**
  12. * Colossus simulator class.
  13. */
  14. export class ColossusComputer {
  15. /**
  16. * Construct a Colossus.
  17. *
  18. * @param {string} ciphertext
  19. * @param {string} pattern - named pattern of Chi, Mu and Psi wheels
  20. * @param {Object} qbusin - which data inputs are being sent to q bus - each can be null, plain or delta
  21. * @param {Object[]} qbusswitches - Q bus calculation switches, multiple rows
  22. * @param {Object} control - control switches which specify stepping modes
  23. * @param {Object} starts - rotor start positions
  24. */
  25. constructor(ciphertext, pattern, qbusin, qbusswitches, control, starts, settotal, limit) {
  26. this.ITAlookup = ITA2_TABLE;
  27. this.REVERSE_ITAlookup = {};
  28. for (const letter in this.ITAlookup) {
  29. const code = this.ITAlookup[letter];
  30. this.REVERSE_ITAlookup[code] = letter;
  31. }
  32. this.initThyratrons(pattern);
  33. this.ciphertext = ciphertext;
  34. this.qbusin = qbusin;
  35. this.qbusswitches = qbusswitches;
  36. this.control = control;
  37. this.starts = starts;
  38. this.settotal = settotal;
  39. this.limitations = limit;
  40. this.allCounters = [0, 0, 0, 0, 0];
  41. this.Zbits = [0, 0, 0, 0, 0]; // Z input is the cipher tape
  42. this.ZbitsOneBack = [0, 0, 0, 0, 0]; // for delta
  43. this.Qbits = [0, 0, 0, 0, 0]; // input generated for placing onto the Q-bus (the logic processor)
  44. this.Xbits = [0, 0, 0, 0, 0]; // X is the Chi wheel bits
  45. this.Xptr = [0, 0, 0, 0, 0]; // pointers to the current X bits (Chi wheels)
  46. this.XbitsOneBack = [0, 0, 0, 0, 0]; // the X bits one back (for delta)
  47. this.Sbits = [0, 0, 0, 0, 0]; // S is the Psi wheel bits
  48. this.Sptr = [0, 0, 0, 0, 0]; // pointers to the current S bits (Psi wheels)
  49. this.SbitsOneBack = [0, 0, 0, 0, 0]; // the S bits one back (for delta)
  50. this.Mptr = [0, 0];
  51. this.rotorPtrs = {};
  52. this.totalmotor = 0;
  53. }
  54. /**
  55. * Begin a run
  56. * @returns {object} -
  57. */
  58. run() {
  59. let result = {
  60. printout: ""
  61. };
  62. // loop until our start positions are back to the beginning
  63. 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};
  64. //this.rotorPtrs = this.starts;
  65. let runcount = 1;
  66. const fast = this.control.fast;
  67. const slow = this.control.slow;
  68. // Print Headers
  69. result.printout += fast + " " + slow + "\n";
  70. do {
  71. this.allCounters = [0, 0, 0, 0, 0];
  72. this.ZbitsOneBack = [0, 0, 0, 0, 0];
  73. this.XbitsOneBack = [0, 0, 0, 0, 0];
  74. // Run full tape loop and process counters
  75. this.runTape();
  76. // Only print result if larger than set total
  77. var fastRef = "00";
  78. var slowRef = "00";
  79. if (fast !== "") fastRef = this.leftPad(this.rotorPtrs[fast],2);
  80. if (slow !== "") slowRef = this.leftPad(this.rotorPtrs[slow],2);
  81. var printline = '';
  82. for (let c=0;c<5;c++) {
  83. if (this.allCounters[c] > this.settotal) {
  84. printline += String.fromCharCode(c+97) + this.allCounters[c]+" ";
  85. }
  86. }
  87. if (printline !== "") {
  88. result.printout += fastRef + " " + slowRef + " : ";
  89. result.printout += printline;
  90. result.printout += "\n";
  91. }
  92. // Step fast rotor if required
  93. if (fast != '') {
  94. this.rotorPtrs[fast]++;
  95. if (this.rotorPtrs[fast] > ROTOR_SIZES[fast]) this.rotorPtrs[fast] = 1;
  96. }
  97. // Step slow rotor if fast rotor has returned to initial start position
  98. if (slow != '' && this.rotorPtrs[fast] === this.starts[fast]) {
  99. this.rotorPtrs[slow]++;
  100. if (this.rotorPtrs[slow] > ROTOR_SIZES[slow]) this.rotorPtrs[slow] = 1;
  101. }
  102. runcount++;
  103. } while (JSON.stringify(this.rotorPtrs) !== JSON.stringify(this.starts));
  104. result.counters = this.allCounters;
  105. result.runcount = runcount;
  106. return result;
  107. };
  108. /**
  109. * Run tape loop
  110. */
  111. runTape() {
  112. let charZin = "";
  113. this.Xptr = [this.rotorPtrs.X1, this.rotorPtrs.X2, this.rotorPtrs.X3, this.rotorPtrs.X4, this.rotorPtrs.X5];
  114. this.Mptr = [this.rotorPtrs.M37, this.rotorPtrs.M61];
  115. this.Sptr = [this.rotorPtrs.S1, this.rotorPtrs.S2, this.rotorPtrs.S3, this.rotorPtrs.S4, this.rotorPtrs.S5];
  116. //console.log(this.Xptr);
  117. // Run full loop of all character on the input tape (Z)
  118. for (let i=0; i<this.ciphertext.length; i++) {
  119. charZin = this.ciphertext.charAt(i);
  120. //console.log('reading tape char #'+i+' : '+charZin);
  121. // Firstly, we check what inputs are specified on the Q-bus input switches
  122. this.getQbusInputs(charZin);
  123. /*
  124. * Pattern conditions on individual impulses. Matching patterns of bits on the Q bus.
  125. * This is the top section on Colussus K rack - the Q bus programming switches
  126. */
  127. this.runQbusProcessingConditional();
  128. /*
  129. * Addition of impulses.
  130. * This is the bottom section of Colossus K rack.
  131. */
  132. this.runQbusProcessingAddition();
  133. // Step rotors
  134. this.stepThyratrons();
  135. }
  136. }
  137. /**
  138. * Step thyratron rings to simulate movement of Lorenz rotors
  139. * Chi rotors all step one per character
  140. * Motor M61 rotor steps one per character, M37 steps dependant on M61 setting
  141. * Psi rotors only step dependant on M37 setting + limitation
  142. */
  143. stepThyratrons() {
  144. let X2bPtr = this.Xptr[1]-1;
  145. if (X2bPtr==0) X2bPtr = ROTOR_SIZES.X2;
  146. let S1bPtr = this.Sptr[0]-1;
  147. if (S1bPtr==0) S1bPtr = ROTOR_SIZES.S1;
  148. const x2sw = this.limitations.X2;
  149. const s1sw = this.limitations.S1;
  150. // Limitation calculations
  151. var lim=1;
  152. if (x2sw) lim = this.rings.X[2][X2bPtr-1];
  153. if (s1sw) {
  154. lim = lim ^ this.rings.S[1][S1bPtr-1];
  155. }
  156. //console.log(this.Mptr);
  157. //console.log(this.rings.M[2]);
  158. const basicmotor = this.rings.M[2][this.Mptr[0]-1];
  159. this.totalmotor = basicmotor;
  160. if (x2sw || s1sw) {
  161. if (basicmotor===0 && lim===1) {
  162. this.totalmotor = 0;
  163. } else {
  164. this.totalmotor = 1;
  165. }
  166. }
  167. //console.log('BM='+basicmotor+', TM='+this.totalmotor);
  168. // Step Chi rotors
  169. for (let r=0; r<5; r++) {
  170. this.Xptr[r]++;
  171. if (this.Xptr[r] > ROTOR_SIZES["X"+(r+1)]) this.Xptr[r] = 1;
  172. }
  173. if (this.totalmotor) {
  174. //console.log('step Psi');
  175. // Step Psi rotors
  176. for (let r=0; r<5; r++) {
  177. this.Sptr[r]++;
  178. if (this.Sptr[r] > ROTOR_SIZES["S"+(r+1)]) this.Sptr[r] = 1;
  179. }
  180. }
  181. // Move M37 rotor if M61 set
  182. if (this.rings.M[1][this.Mptr[1]-1]==1) this.Mptr[0]++;
  183. if (this.Mptr[0] > ROTOR_SIZES.M37) this.Mptr[0]=1;
  184. // Always move M61 rotor
  185. this.Mptr[1]++;
  186. if (this.Mptr[1] > ROTOR_SIZES.M61) this.Mptr[1]=1;
  187. }
  188. /**
  189. * Get Q bus inputs
  190. */
  191. getQbusInputs(charZin) {
  192. // Zbits - the bits from the current character from the cipher tape.
  193. this.Zbits = this.ITAlookup[charZin].split("");
  194. //console.log('Zbits = '+this.Zbits);
  195. if (this.qbusin.Z == 'Z') {
  196. // direct Z
  197. this.Qbits = this.Zbits;
  198. //console.log('direct Z: Qbits = '+this.Qbits);
  199. } else if(this.qbusin.Z == 'ΔZ') {
  200. // delta Z, the Bitwise XOR of this character Zbits + last character Zbits
  201. for(let b=0;b<5;b++) {
  202. this.Qbits[b] = this.Zbits[b] ^ this.ZbitsOneBack[b];
  203. }
  204. //console.log('delta Z: Qbits = '+this.Qbits);
  205. }
  206. this.ZbitsOneBack = this.Zbits.slice(); // copy value of object, not reference
  207. //console.log('Zin::Q bus now '+this.Qbits+'['+this.REVERSE_ITAlookup[this.Qbits.join("")]+']');
  208. // Xbits - the current Chi wheel bits
  209. //console.log(this.rings.X);
  210. //console.log('Xptr = '+this.Xptr);
  211. for (let b=0;b<5;b++) {
  212. this.Xbits[b] = this.rings.X[b+1][this.Xptr[b]-1];
  213. }
  214. if (this.qbusin.Chi != "") {
  215. //console.log('X Bits '+this.Xbits+'['+this.REVERSE_ITAlookup[this.Xbits.join("")]+']');
  216. //console.log('X Char = ' + this.REVERSE_ITAlookup[this.Xbits.join("")]);
  217. if (this.qbusin.Chi == "Χ") {
  218. // direct X added to Qbits
  219. for (let b=0;b<5;b++) {
  220. this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b];
  221. }
  222. //console.log('direct X: Qbits = '+this.Qbits);
  223. } else if(this.qbusin.Chi == "ΔΧ") {
  224. // delta X
  225. for(let b=0;b<5;b++) {
  226. this.Qbits[b] = this.Qbits[b] ^ this.Xbits[b];
  227. this.Qbits[b] = this.Qbits[b] ^ this.XbitsOneBack[b];
  228. }
  229. //console.log('delta X: Xbits = '+this.Xbits+' Xbits-1 = '+this.XbitsOneBack);
  230. //console.log('delta X: Qbits = '+this.Qbits);
  231. }
  232. }
  233. this.XbitsOneBack = this.Xbits.slice();
  234. //console.log('setting XbitsOneBack to '+this.Xbits);
  235. //console.log('Zin+Xin::Q bus now '+this.Qbits+'['+this.REVERSE_ITAlookup[this.Qbits.join("")]+']');
  236. // Sbits - the current Psi wheel bits
  237. //console.log(this.rings.S);
  238. //console.log('Sptr = '+this.Sptr);
  239. for (let b=0;b<5;b++) {
  240. this.Sbits[b] = this.rings.S[b+1][this.Sptr[b]-1];
  241. }
  242. if (this.qbusin.Psi != "") {
  243. //console.log('S Bits '+this.Sbits+'['+this.REVERSE_ITAlookup[this.Sbits.join("")]+']');
  244. //console.log('S Char = ' + this.REVERSE_ITAlookup[this.Sbits.join("")]);
  245. if(this.qbusin.Psi == "Ψ") {
  246. // direct S added to Qbits
  247. for (let b=0;b<5;b++) {
  248. this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b];
  249. }
  250. //console.log('direct S: Qbits = '+this.Qbits);
  251. } else if(this.qbusin.Psi == "ΔΨ") {
  252. // delta S
  253. for (let b=0;b<5;b++) {
  254. this.Qbits[b] = this.Qbits[b] ^ this.Sbits[b];
  255. this.Qbits[b] = this.Qbits[b] ^ this.SbitsOneBack[b];
  256. }
  257. //console.log('delta S: Qbits = '+this.Qbits);
  258. }
  259. }
  260. this.SbitsOneBack = this.Sbits.slice();
  261. //console.log('Q bus now '+this.Qbits+'['+this.REVERSE_ITAlookup[this.Qbits.join("")]+']');
  262. }
  263. /**
  264. * Conditional impulse Q bus section
  265. */
  266. runQbusProcessingConditional() {
  267. let cnt = [-1, -1, -1, -1, -1];
  268. const numrows = this.qbusswitches.condition.length;
  269. for (let r=0;r<numrows;r++) {
  270. let row = this.qbusswitches.condition[r];
  271. if (row.Counter !== "") {
  272. let result = true;
  273. let cPnt = row.Counter-1;
  274. const Qswitch = this.readBusSwitches(row.Qswitches);
  275. // Match switches to bit pattern
  276. for (let s=0;s<5;s++) {
  277. if (Qswitch[s] >= 0 && Qswitch[s] != this.Qbits[s]) result = false;
  278. }
  279. // Check for NOT switch
  280. if (row.Negate) result = !result;
  281. // AND each row to get final result
  282. if (cnt[cPnt] == -1) {
  283. cnt[cPnt] = result;
  284. } else if (result==0) {
  285. cnt[cPnt] = 0;
  286. }
  287. }
  288. }
  289. // Negate the whole column, this allows A OR B by doing NOT(NOT A AND NOT B)
  290. for (let c=0;c<5;c++) {
  291. if (this.qbusswitches.condNegateAll) cnt[c] = !cnt[c];
  292. if (this.qbusswitches.totalMotor === "" || (this.qbusswitches.totalMotor === "x" && this.totalmotor == 0) || (this.qbusswitches.totalMotor === "." && this.totalmotor == 1)) {
  293. if (cnt[c]==1) this.allCounters[c]++;
  294. }
  295. }
  296. }
  297. /**
  298. * Addition of impulses Q bus section
  299. */
  300. runQbusProcessingAddition() {
  301. let row = this.qbusswitches.addition[0];
  302. const Qswitch = row.Qswitches.slice();
  303. if (row.C1) {
  304. let addition = 0;
  305. for (let s=0;s<5;s++) {
  306. // XOR addition
  307. if (Qswitch[s]) {
  308. addition = addition ^ this.Qbits[s];
  309. }
  310. }
  311. const equals = (row.Equals==""?-1:(row.Equals=="."?0:1));
  312. if (addition == equals) {
  313. this.allCounters[0]++;
  314. }
  315. }
  316. //console.log("counter1="+this.allCounters[0]);
  317. }
  318. /**
  319. * Initialise thyratron rings
  320. * These hold the pattern of 1s & 0s for each rotor on banks of thyraton GT1C valves which act as a one-bit store.
  321. */
  322. initThyratrons(pattern) {
  323. this.rings = {
  324. X: {
  325. 1: INIT_PATTERNS[pattern].X[1].slice().reverse(),
  326. 2: INIT_PATTERNS[pattern].X[2].slice().reverse(),
  327. 3: INIT_PATTERNS[pattern].X[3].slice().reverse(),
  328. 4: INIT_PATTERNS[pattern].X[4].slice().reverse(),
  329. 5: INIT_PATTERNS[pattern].X[5].slice().reverse()
  330. },
  331. M: {
  332. 1: INIT_PATTERNS[pattern].M[1].slice().reverse(),
  333. 2: INIT_PATTERNS[pattern].M[2].slice().reverse(),
  334. },
  335. S: {
  336. 1: INIT_PATTERNS[pattern].S[1].slice().reverse(),
  337. 2: INIT_PATTERNS[pattern].S[2].slice().reverse(),
  338. 3: INIT_PATTERNS[pattern].S[3].slice().reverse(),
  339. 4: INIT_PATTERNS[pattern].S[4].slice().reverse(),
  340. 5: INIT_PATTERNS[pattern].S[5].slice().reverse()
  341. }
  342. };
  343. }
  344. /**
  345. * Left Pad number
  346. */
  347. leftPad(number, targetLength) {
  348. let output = number + "";
  349. while (output.length < targetLength) {
  350. output = "0" + output;
  351. }
  352. return output;
  353. }
  354. /**
  355. * Read argument bus switches X & . and convert to 1 & 0
  356. */
  357. readBusSwitches(row) {
  358. let output = [-1, -1, -1, -1, -1];
  359. for (let c=0;c<5;c++) {
  360. if (row[c]===".") output[c] = 0;
  361. if (row[c]==="x") output[c] = 1;
  362. }
  363. return output;
  364. }
  365. }