-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbrain.js
More file actions
493 lines (477 loc) · 20.2 KB
/
brain.js
File metadata and controls
493 lines (477 loc) · 20.2 KB
1
2
3
4
5
6
7
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
var Standards = { general: { options: { automation: "none" } } };
// importScripts("https://epicenterprograms.github.io/standards/behavior/general.js"); // This stopped working for some reason.
Standards.general.getType = function (item) { // I needed this for Standards.general.forEach().
/**
finds the type of an item since it's unnecessarily complicated to be sure normally
extra arguments can be added to check against special types first
each argument must be a string representation of the constructor
checks are done with instanceof
non-native functions = none
*/
var extraTypes = Array.prototype.slice.call(arguments, 1);
var reverseIndex = extraTypes.length;
if (reverseIndex > 0) {
while (reverseIndex--) {
let type = extraTypes[reverseIndex];
if (type && type.constructor === String && type.search(/[^\w.()]/) === -1) {
try {
if (item instanceof eval(type)) {
return type;
}
} catch (error) {
console.warn('There was a problem evaluating the type of "' + type + '".');
}
}
}
}
if (item === undefined) { // if it's undefined
/// undeclared variables won't make it to this function
/// typeof item === "undefined" checks whether a variable exists
return "undefined";
} else if (item === null) { // if it's null
return "null";
} else if (item.constructor === Number && isNaN(item)) { // if it's not a number
return "NaN";
} else if (item.constructor.toString().search(/function HTML\w*Element\(\) \{ \[native code\] \}/) > -1) { // if it's an HTML element
return "HTMLElement";
} else if (item instanceof Error) {
return "Error";
} else {
let match = item.constructor.toString().match(/^function (\w+)\(\)/);
if (match === null) {
console.error(TypeError("The item has an unknown type."));
console.log(item.constructor.toString());
console.log(item.constructor);
return undefined;
} else {
return match[1];
}
}
};
Standards.general.forEach = function (list, doStuff, shouldCopy) { // This is the only function I use from Standards, so I copied it over.
/**
does stuff for every item of an iterable list (or object)
arguments:
list = the iterable to go through
doStuff = a function to be run for every item in the list
arguments put in the function:
if an iterable list (Array, HTMLCollection, String, ...): item, index, list
if an object/dictionary: value, key, object, itemIndex
if a number: number-index, index, number
can return "break" to stop execution of the function
shouldCopy = a copy should be worked with
doesn't alter the original list
non-native functions = getType
*/
if (Standards.general.getType(doStuff) != "Function") {
throw "The second arument provided in Standards.general.forEach (" + doStuff + ") isn't a function.";
}
let index = 0;
let returnValue;
if (Standards.general.getType(list) == "Object") {
let associativeList,
keys = Object.keys(list);
shouldCopy = shouldCopy === undefined ? false : shouldCopy;
if (shouldCopy) {
associativeList = JSON.parse(JSON.stringify(list));
} else {
associativeList = list;
}
while (index < keys.length) {
returnValue = doStuff(associativeList[keys[index]], keys[index], associativeList, index);
if (returnValue == "break") {
break;
} else {
index++;
}
}
/// Using Object.keys() and a while loop is about 100 times faster than a for...in... loop.
/// That's not to mention the fact that this.propertyIsEnumerable() would also need to be used which is also slow.
/// This is still about 10 times slower than looping through things with number indicies, though.
/// (These time comparisons are based on usage outside of this function;
/// doing things by referencing a function makes things about 10 times longer.)
} else if (Standards.general.getType(list[Symbol.iterator]) == "Function" || list instanceof HTMLCollection) {
/// Microsoft Edge doesn't think HTMLCollections have Symbol.iterator
//// check this in Microsoft Edge again
let item;
if (shouldCopy) {
let items = [];
for (item of list) {
items.push(item);
}
for (item of items) {
returnValue = doStuff(item, index, items);
if (returnValue == "break") {
break;
}
index++;
}
} else {
for (item of list) {
returnValue = doStuff(item, index, list);
if (returnValue == "break") {
break;
}
index++;
}
}
} else if (Standards.general.getType(list) == "Number") {
while (index < list) {
returnValue = doStuff(list - index, index, list);
if (returnValue == "break") {
break;
} else {
index++;
}
}
} else {
throw "The item provided (" + list + ") isn't iterable.";
}
//// add a function type option
};
var S = Standards.general;
var messenger = {}; // sends information back to the website
var words = [[]]; // holds every English word in arrays in order of increasing word length
var verbs = [[]]; // holds every verb in arrays in order of increasing word length
var ALPHABET = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
var PUNCTUATION = [".", "?", "!", ",", "'", '"', ";", ":", "-", "(", ")", "/"];
var LONERS = [
["and", "but"],
["am", "is", "are", "was", "were", "be", "been"],
["of", "for", "with", "from"]
];
var solutions = []; // holds all possible solutions
var filtered = {};
var hidden = [];
var solutionIndex = 0; // holds which solution is to be displayed
function isolate(word) {
/**
removes the puctuation from words
*/
var newWord = "";
S.forEach(word, function (character, index) {
if (character.toLowerCase() != character.toUpperCase()/*|| character=="'"*/) { // if the character is a letter
newWord += character;
} else if (character == "{" && word.indexOf("}", index) > -1) {
newWord += word.slice(index, word.indexOf("}", index) + 1);
}/* else {
// do nothing
// (gets rid of non-letters)
} */
});
return newWord;
}
function standardize(word) {
/**
makes a word have a standard letter pattern
(alphabetically assigns different letters to different letters and same letters to same letters)
symbols comprised of {number} are considered one symbol
examples:
hello --> abccd
awesome --> abcdefc
qxf{32}xb --> abcdbe
*/
var uniqueLetters = {};
S.forEach(word, function (letter, index, word) {
if (letter.toLowerCase() != letter.toUpperCase()) {
if (uniqueLetters[letter]) {
uniqueLetters[letter].push(index);
} else {
uniqueLetters[letter] = [index];
}
} else if (letter == "{") {
if (uniqueLetters["~" + word.slice(index + 1, word.indexOf("}", index))]) {
uniqueLetters["~" + word.slice(index + 1, word.indexOf("}", index))].push(index);
} else {
uniqueLetters["~" + word.slice(index + 1, word.indexOf("}", index))] = [index];
}
}/* else {
// do nothing
// (skips the number between any {}s)
} */
});
var letters = word.split("");
var index = 0;
var shift = 0;
var oldShift;
S.forEach(uniqueLetters, function (numbers) { // This could likely be done with a search-and-replace method. (although going straight to the index is probably faster than a searching method)
numbers.forEach(function (number) {
if (letters[number + shift].toLowerCase() != letters[number + shift].toUpperCase()) {
if (index < 26) {
letters.splice(number + shift, 1, ALPHABET[index]);
} else { // This should never happen.
oldShift = shift;
shift += ("{" + "{0}").format(index - 26).length; // two braces can't be back-to-back without doing weird stuff
letters.splice(number + oldShift, 1, "{" + (index - 26) + "}");
}
} else if (letters[number + shift] == "{") {
oldShift = shift;
if (index < 26) {
shift -= letters.indexOf("}", number + shift) - (number + shift); // This needs to happen first or else the splice changes things.
letters.splice(number + oldShift, letters.indexOf("}", number + oldShift) - (number + oldShift) + 1, ALPHABET[index]);
} else {
shift += ("{" + "{0}").format(index - 26).length - letters.indexOf("}", number + shift) + (number + shift);
letters.splice(number + oldShift, letters.indexOf("}", number + oldShift) - (number + oldShift) + 1, "{" + (index - 26) + "}");
}
}
});
index++;
});
word = "";
letters.forEach(function (letter) {
word += letter;
});
return word;
}
function checkSolution(solution) {
/**
checks basic grammar of a potential solution
reduces nonsensical solutions
*/
let passes = true;
//// solution.split(/\.|\?|!/g).forEach(function (sentence) {
//// if (sentence.trim() != "") { // ensures sentences ending in punctuation don't fail the solution
if (solution.length > 3) { // The text should have a verb if it's longer than 3 words long.
if (!solution.some(function (solutionWord) { // if the solution has a verb
return verbs[solutionWord.length - 1].includes(solutionWord);
})) {
passes = false;
}
}
//// }
//// });
/*
solution.split(/\.|\?|!|,|'|"|;|:|-|\(|\)|\//g).forEach(function (phrase) {
phrase = phrase.split(" ");
if (phrase.length > 1) {
//// check whether LONERs are next to each other
}
});
*/
return passes;
}
self.addEventListener("message", function (message) {
var time = performance.now(); //// tracks the performance of the decoder
new Promise(function (resolve, reject) {
solutions = [];
var codeText = message.data.text; // holds the encoded text
words = message.data.words;
verbs = message.data.verbs;
var values = {}; // keeps track of data related to the code symbols (appearances, possibilities, and strikes)
var strikeOut = 1; // the number of strikes against a character it takes to have it removed
S.forEach(codeText, function (character, index) { // finds all of the unique letters in the text and their locations
if (character != character.toUpperCase()) { // if it's a letter
if (values.hasOwnProperty(character)) {
values[character].appearances.push(index);
} else {
values[character] = { appearances: [index], codeWords: [], possibilities: [], strikes: [] };
}
} //// There needs to be something to handle numbers in {}s.
});
/// "values" now has an object for every unique symbol in the encoded text
/// with an "appearances" property containing indices of appearance for each unique symbol
codeText = codeText.split(" ").sort(function (a, b) { return a.length - b.length });
/// "codeText" is now an array in order of increasing word length
var codeWords = {}; // will hold a list of possible decoded words for every unique word
S.forEach(codeText, function (word) {
/// isolate() removes the punctuation from the word
codeWords[isolate(word)] = []; // assignment might not work if there's {}s
});
/// "codeWords" now has an empty array for every word in the encoded text
/// and every word in the encoded text is in order of increasing length
/// (smaller words have less possible words? which is helpful to start with later) ////
S.forEach(codeWords, function (array, key) { // finds all of the words with the same letter pattern as the code words
var standardizedWord = standardize(key); // Doing this only once (not at every if-statement) speeds things up.
words[key.length - 1].forEach(function (word) {
if (standardizedWord == standardize(word)) {
array.push(word);
}
});
});
/// "codeWords" now has an array containing a list of all English words with the same letter pattern as their respective word from the encoded text
Object.keys(values).forEach(function (symbol) { // for every unique symbol
S.forEach(codeWords, function (possibleWords, text) { // for every code word
if (text.includes(symbol)) { // if the current code word includes the current symbol
// for every code word that contains a certain symbol
values[symbol].codeWords.push(text); // add the code word to the codeWords list of the symbol
}
});
});
/// now every symbol in "values" is associated with a list of code words where that symbol is contained
S.forEach(values, function (properties, symbol) { // for every unique symbol
let symbolIndex = properties.codeWords[0].indexOf(symbol); // where the symbol is in the code word (more than one place isn't needed)
// adds all letters the symbol could be
codeWords[properties.codeWords[0]].forEach(function (possibility) {
if (!values[symbol].possibilities.includes(possibility[symbolIndex])) {
values[symbol].possibilities.push(possibility[symbolIndex]);
}
});
/// This samples the first code word where the symbol is present and
/// goes through all of the possible English words associated with the sampled code word.
/// If the corresponding letter of the English word isn't already listed as a possible symbol decryption,
/// the letter is added to the possibilities associated with the given symbol.
/// This creates a preliminary list to work with in the next code block.
});
/// now every symbol in "values" has a preliminary set of possible decryptions
// determines which letters are valid values for each unique symbol in the encoded text across all words
// (when a symbol never equals a certain letter in any possibility for one word, any possibilities for other words using that letter must be eliminated)
// (determines what symbol decryptions make sense within words)
let changed; // whether any of the following processing has caused a change in the possible words
do {
changed = false;
S.forEach(values, function (properties, symbol) { // for every unique symbol
properties.codeWords.forEach(function (word) { // for every code word that contains the symbol
// sets the possible decryptions of the current symbol based on the possibilities associated with the code word
let symbolIndex = word.indexOf(symbol); // where the symbol is in the code word (more than one place isn't needed)
let codeLetters = []; // will hold possible decryptions of the current symbol
codeWords[word].forEach(function (possibility) {
if (!codeLetters.includes(possibility[symbolIndex])) {
codeLetters.push(possibility[symbolIndex]);
}
});
S.forEach(values[symbol].possibilities, function (possibility) { // This can't use the native array function because I need it to copy.
if (!codeLetters.includes(possibility)) {
// remove the possibility from "values"
values[symbol].possibilities.splice(values[symbol].possibilities.indexOf(possibility), 1);
// remove the relavent words from "codeWords"
properties.codeWords.forEach(function (codeWord) {
let newSymbolIndex = codeWord.indexOf(symbol);
S.forEach(codeWords[codeWord], function (otherWord) { // This can't use the native array function because I need it to copy.
if (otherWord[newSymbolIndex] == possibility) { // if the code word possibility includes the eliminated letter
codeWords[codeWord].splice(codeWords[codeWord].indexOf(otherWord), 1); // remove the possibility from "codeWords"
}
}, true);
});
changed = true; // indicates that the possible decryption has changed
}
}, true);
});
});
} while (changed)
/// values now has, for each unique symbol in the encrypted text:
/// "appearances" corresponding to the indices of the text where the symbol is found (done earlier)
/// "codeWords" corresponding to the code words that contain the symbol (done earlier)
/// "possibilities" corresponding to a list of all the letters the symbol could be
//// "strikes" corresponding to a list of strikes made in the same order as the possibilities indicating the likelihood of a symbol being a certain letter
////
//// codeWords now only has words which contain letters that don't have too many strikes against them ////
// determines whether there's any solution possible
S.forEach(values, function (properties, symbol) {
if (properties.possibilities.length == 0) {
console.warn('The symbol "' + symbol + '" has no possibilities left.');
}
});
codeText = message.data.text.split(" ");
codeText.forEach(function (word, index) {
codeText[index] = isolate(word);
});
var totalIndexList = [], // will hold the maximum index of the possibilities for each encoded word
currentIndexList = []; // will hold a list of indices corresponding to the current possibility of each word being investigated
codeText.forEach(function (word) { // sets totalIndexList and currentIndexList
totalIndexList.push(codeWords[word].length - 1);
currentIndexList.push(0);
});
var trueFalse = true, // holds the result of complicated processing which determines whether the following decoding loop should continue
index = 0, // which code word is being looked at
codeKey = {},
solution; // the current solution being constructed
S.forEach(values, function (unnecessary, key) {
codeKey[key] = undefined;
});
console.info("After refining eligible words:"); ////
let totalPossibilities = 1;
codeText.forEach(function (word, number) { //// This logs how many possibilities each word has at this point.
totalPossibilities *= codeWords[word].length;
console.info("Possibilities for word " + (number + 1) + " = " + codeWords[word].length);
});
console.info("Total possibilites = " + totalPossibilities);
function usageCheck(letters) { //// This needs to allow for imperfect matches.
// makes sure a word doesn't conflict with the letters used in previous words
let falseTrue = true;
let changes = [];
let placement = 0;
while (placement < letters.length) { // goes through every letter
let letter = letters[placement];
let codeLetter = codeText[index][placement];
if (codeKey[codeLetter] == undefined || index <= codeKey[codeLetter].maximumIndex) {
let unused = true;
let keys = Object.keys(codeKey);
let codeKeyIndex = 0;
while (codeKeyIndex < keys.length) { // checks if the letter is being used in a preceeding word
let guess = codeKey[keys[codeKeyIndex]];
if (guess != undefined && guess.letter == letter && guess.maximumIndex < index) {
unused = false;
break;
}
codeKeyIndex++;
}
if (unused) {
changes.push([codeLetter, letter]);
} else {
falseTrue = false;
break;
}
} else if (codeKey[codeLetter].letter != letter) { //// This could be where you allow for imperfect matches.
falseTrue = false;
break;
}
placement++;
}
if (falseTrue) { // updates the code key with the letters of the word if it passes
let changeIndex = 0;
while (changeIndex < changes.length) {
codeKey[changes[changeIndex][0]] = { "letter": changes[changeIndex][1], "maximumIndex": index };
changeIndex++;
}
}
return falseTrue;
}
// determines which symbol decryptions work across words
while (trueFalse) { // **** This is the most taxing part of the decoder. ****
if (currentIndexList[index] <= totalIndexList[index]) {
if (usageCheck(codeWords[codeText[index]][currentIndexList[index]])) { // if the word doesn't conflict with the previously used letters
if (index < currentIndexList.length - 1) { // if the index isn't at the end of the array (if every word hasn't been checked yet)
index++; // go to the next word of the encoded text
messenger.progress = (currentIndexList[0] + 1) / (totalIndexList[0] + 1) * 100 - .1;
self.postMessage(messenger); // gives the website an update up the progress
} else {
solution = [];
let number = 0;
while (number < codeText.length) {
let word = codeText[number];
solution.push(codeWords[word][currentIndexList[number]]);
number++;
}
if (checkSolution(solution)) {
solutions.push(solution.join(" "));
}
currentIndexList[index]++;
}
} else {
currentIndexList[index]++;
}
} else {
if (index > 0) {
currentIndexList[index] = 0;
index--;
currentIndexList[index]++;
} else {
trueFalse = false;
}
}
}
resolve();
}).catch(function (error) {
console.error(error);
messenger.error = true;
messenger = JSON.parse(JSON.stringify(messenger));
self.postMessage(messenger);
}).then(function () {
messenger.progress = 100;
messenger.solutions = solutions;
messenger.time = Math.round(performance.now() - time) / 1000;
messenger = JSON.parse(JSON.stringify(messenger));
self.postMessage(messenger);
// self.close(); // closes the web worker
});
});