squanlib 1.0.0 → 1.1.0
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 +50 -1
- package/dist/squanlib.umd.js +1805 -0
- package/dist/squanlib.umd.min.js +2 -0
- package/package.json +19 -5
- package/squanlib.js +837 -91
package/squanlib.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export default class SquanLib {
|
|
6
6
|
|
|
7
7
|
// =========================================================================
|
|
8
|
-
// SECTION 1
|
|
8
|
+
// SECTION 1: DATA TABLES
|
|
9
9
|
// =========================================================================
|
|
10
10
|
|
|
11
11
|
// -------------------------------------------------------------------------
|
|
@@ -129,7 +129,7 @@ export default class SquanLib {
|
|
|
129
129
|
]);
|
|
130
130
|
|
|
131
131
|
// -------------------------------------------------------------------------
|
|
132
|
-
// wcaToBaseKarn
|
|
132
|
+
// wcaToBaseKarn: maps single WCA move → base karn.
|
|
133
133
|
// -------------------------------------------------------------------------
|
|
134
134
|
static wcaToBaseKarn = {
|
|
135
135
|
// ── compound numeric → single karn ────────────────────────────────────
|
|
@@ -157,7 +157,7 @@ export default class SquanLib {
|
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
// -------------------------------------------------------------------------
|
|
160
|
-
// baseKarnToHighKarn
|
|
160
|
+
// baseKarnToHighKarn: longest first, base karn → high karn
|
|
161
161
|
// -------------------------------------------------------------------------
|
|
162
162
|
static baseKarnToHighKarn = {
|
|
163
163
|
"U U' U U'": "U4", "U' U U' U": "U4'",
|
|
@@ -183,8 +183,8 @@ export default class SquanLib {
|
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
|
-
* A_MOVES
|
|
187
|
-
* a_MOVES
|
|
186
|
+
* A_MOVES: legal moves available for top misalign
|
|
187
|
+
* a_MOVES: legal moves available for bottom misalign
|
|
188
188
|
*/
|
|
189
189
|
static A_MOVES = [
|
|
190
190
|
[3, 0], [-3, 0], [0, 3], [0, -3], [3, 3],
|
|
@@ -196,7 +196,7 @@ export default class SquanLib {
|
|
|
196
196
|
];
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* OPTIM
|
|
199
|
+
* OPTIM: table for optimizable moves
|
|
200
200
|
*/
|
|
201
201
|
static OPTIM = {
|
|
202
202
|
// special case
|
|
@@ -214,7 +214,7 @@ export default class SquanLib {
|
|
|
214
214
|
};
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
|
-
* CLOSEST_MAP
|
|
217
|
+
* CLOSEST_MAP: maps a -5~6 turn to the its closest 3n move
|
|
218
218
|
*/
|
|
219
219
|
static CLOSEST_MAP = new Map([
|
|
220
220
|
[-5, -6], [-4, -3], [-3, -3], [-2, -3], [-1, 0], [0, 0],
|
|
@@ -222,7 +222,7 @@ export default class SquanLib {
|
|
|
222
222
|
]);
|
|
223
223
|
|
|
224
224
|
/**
|
|
225
|
-
* MOVE_VALUES
|
|
225
|
+
* MOVE_VALUES: lookup table for the ergonomic rating of each individual move.
|
|
226
226
|
* A = 10, a = 0-1
|
|
227
227
|
* / = upslice, \ = downslice
|
|
228
228
|
*/
|
|
@@ -247,7 +247,7 @@ export default class SquanLib {
|
|
|
247
247
|
['a\\3,0', 16], ['a\\3,3', 11], ['a\\3,6', 4], ['a\\3,-3', 6],
|
|
248
248
|
['a\\6,0', 4], ['a\\6,3', 2], ['a\\6,6', 0], ['a\\6,-3', 1],
|
|
249
249
|
['a\\-3,0', 15], ['a\\-3,3', 10], ['a\\-3,6', 2], ['a\\-3,-3', 5],
|
|
250
|
-
// fractional (non-multiple-of-3) moves
|
|
250
|
+
// fractional (non-multiple-of-3) moves: alignment prefix omitted
|
|
251
251
|
['/1,-2', 4], ['\\1,-2', 17], ['/-1,2', 15], ['\\-1,2', 14],
|
|
252
252
|
['/1,-5', 3], ['\\1,-5', 1], ['/-1,5', 8], ['\\-1,5', 3],
|
|
253
253
|
['/1,4', 7], ['\\1,4', 14], ['/-1,-4', 12], ['\\-1,-4', 9],
|
|
@@ -267,7 +267,266 @@ export default class SquanLib {
|
|
|
267
267
|
]);
|
|
268
268
|
|
|
269
269
|
/**
|
|
270
|
-
*
|
|
270
|
+
* GOOD_FINISHES: moves that are acceptable as the last move
|
|
271
|
+
*/
|
|
272
|
+
static GOOD_FINISHES = new Set([
|
|
273
|
+
"11", "-1-1", "22", "-2-2", "2-1", "-21", "1-2", "-12",
|
|
274
|
+
"30", "-30", "03", "0-3", "33", "3-3", "-3-3", "-33",
|
|
275
|
+
"41", "-4-1", "14", "-1-4", "2-4", "-24", "4-2", "-42",
|
|
276
|
+
"5-1", "-51", "-45", "-54", "63",
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* OBLToEnglish: maps CSP-style hex into OBL names
|
|
281
|
+
* (format is 24-character string, both corner first, STARTING FROM TOP RIGHT OF SLICE
|
|
282
|
+
* meaning: 0-1 is the solved position. **the order is like CSP tracing.**)
|
|
283
|
+
*/
|
|
284
|
+
static OBLToEnglish = {
|
|
285
|
+
'BBbBBbBBbBBb': 'solved',
|
|
286
|
+
'BBwWWwWWwWWw': '1c',
|
|
287
|
+
'BBwBBwWWwWWw': 'cadj',
|
|
288
|
+
'BBwWWwBBwWWw': 'copp',
|
|
289
|
+
'BBwBBwBBwWWw': '3c',
|
|
290
|
+
'BBwBBwBBwBBw': '4e',
|
|
291
|
+
'WWbWWbWWbWWw': '3e',
|
|
292
|
+
'WWbWWwWWbWWw': 'line',
|
|
293
|
+
'WWbWWbWWwWWw': 'L',
|
|
294
|
+
'WWbWWwWWwWWw': '1e',
|
|
295
|
+
'WWbBBwWWwWWw': 'left pair', 'BBbWWwWWwWWw': 'right pair',
|
|
296
|
+
'BBwWWwWWbWWw': 'left arrow', 'BBwWWbWWwWWw': 'right arrow',
|
|
297
|
+
'WWbBBbWWwWWw': 'gem',
|
|
298
|
+
'WWwWWbWWbBBw': 'left knight', 'BBbWWbWWwWWw': 'right knight',
|
|
299
|
+
'WWwWWbWWwBBb': 'left axe', 'BBwWWbWWwWWb': 'right axe',
|
|
300
|
+
'BBwWWbWWbWWw': 'squid',
|
|
301
|
+
'WWwWWbBBbWWb': 'left thumb', 'WWbBBbWWwWWb': 'right thumb',
|
|
302
|
+
'WWwBBbWWbWWb': 'left bunny', 'WWbWWbBBwWWb': 'right bunny',
|
|
303
|
+
'BBbBBwWWwWWw': 'shell',
|
|
304
|
+
'BBwWWwWWbBBw': 'left bird', 'BBwBBbWWwWWw': 'right bird',
|
|
305
|
+
'BBwWWbWWwBBw': 'hazard',
|
|
306
|
+
'BBbBBbWWwWWw': 'left kite', 'WWwWWbBBbBBw': 'right kite',
|
|
307
|
+
'BBwBBwWWbWWb': 'left cut', 'BBwBBbWWbWWw': 'right cut',
|
|
308
|
+
'BBbBBwWWbWWw': 'black T', 'WWwWWbBBwBBb': 'white T',
|
|
309
|
+
'WWbBBwWWbBBw': 'left N', 'WWwBBbWWwBBb': 'right N',
|
|
310
|
+
'WWbBBbWWwBBw': 'black tie', 'BBwWWwBBbWWb': 'white tie',
|
|
311
|
+
'BBbWWwBBwWWw': 'left yoshi', 'WWwBBwWWbBBw': 'right yoshi'
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
static OBLToState = Object.fromEntries(
|
|
315
|
+
Object.entries(this.OBLToEnglish).map(([key, value]) => [value, key])
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* NAMING: matt's OBL naming
|
|
320
|
+
*/
|
|
321
|
+
static NAMING = {
|
|
322
|
+
"solved": "O", "1c": "D", "cadj": "J", "copp": "V", "3c": "M", "4e": "Q",
|
|
323
|
+
"3e": "W", "line": "F", "L": "L", "1e": "E", "left pair": "Pw", "right pair": "Pc",
|
|
324
|
+
"left arrow": "Aw", "right arrow": "Ac", "gem": "G", "left knight": "Hw",
|
|
325
|
+
"right knight": "Hc", "left axe": "Xc", "right axe": "Xw", "squid": "S",
|
|
326
|
+
"left thumb": "THw", "right thumb": "THc", "left bunny": "Uc", "right bunny": "Uw",
|
|
327
|
+
"shell": "SH", "left bird": "Bc", "right bird": "Bw", "hazard": "Z",
|
|
328
|
+
"left kite": "Kc", "right kite": "Kw", "left cut": "Cw", "right cut": "Cc",
|
|
329
|
+
"black T": "Tu", "white T": "Td", "left N": "Nw", "right N": "Nc",
|
|
330
|
+
"black tie": "Iu", "white tie": "Id", "left yoshi": "Yc", "right yoshi": "Yw"
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* OBL_ANGLES: the starting angle, logged in PATTERNS
|
|
335
|
+
*/
|
|
336
|
+
static OBL_ANGLES = {
|
|
337
|
+
"solved": "-", "1c": "UR", "cadj": "R",
|
|
338
|
+
"copp": "/", "3c": "DR", "4e": "-",
|
|
339
|
+
"3e": "D", "line": "—", "L": "DR",
|
|
340
|
+
"1e": "R", "left pair": "DR", "right pair": "UR",
|
|
341
|
+
"left arrow": "UR", "right arrow": "UR",
|
|
342
|
+
"gem": "DR", "left knight": "L", "right knight": "R",
|
|
343
|
+
"left axe": "L", "right axe": "R", "squid": "UR",
|
|
344
|
+
"left thumb": "DL", "right thumb": "DR",
|
|
345
|
+
"left bunny": "DR", "right bunny": "DL",
|
|
346
|
+
"shell": "R", "left bird": "R", "right bird": "U",
|
|
347
|
+
"hazard": "U", "left kite": "R", "right kite": "L",
|
|
348
|
+
"left cut": "R", "right cut": "R",
|
|
349
|
+
"black T": "R", "white T": "R",
|
|
350
|
+
"left N": "\\", "right N": "\\",
|
|
351
|
+
"black tie": "DR", "white tie": "DR",
|
|
352
|
+
"left yoshi": "UL", "right yoshi": "UR"
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
static nextAngle = {
|
|
356
|
+
// Single character angles
|
|
357
|
+
"-": "-", "/": "\\", "\\": "/", "—": "|", "|": "—",
|
|
358
|
+
"U": "R", "R": "D", "D": "L", "L": "U",
|
|
359
|
+
// Double character angles
|
|
360
|
+
"UR": "DR", "DR": "DL", "DL": "UL", "UL": "UR"
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
static get HALF_L() { return 6; }
|
|
364
|
+
static get LAYERL() { return 12; }
|
|
365
|
+
static get THREE_FOUR_L() { return 18; }
|
|
366
|
+
static get CUBEL() { return 24; }
|
|
367
|
+
static get SOLVED() { return "bBBbBBbBBbBBwWWwWWwWWwWW"; }
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* POSSIBLE_OBL: every OBL case as [specifier, U, D].
|
|
371
|
+
*/
|
|
372
|
+
static POSSIBLE_OBL = [
|
|
373
|
+
['', 'solved', 'solved'],
|
|
374
|
+
['', '1c', '1c'], ['', 'cadj', 'cadj'], ['', 'cadj', 'copp'], ['', 'copp', 'copp'],
|
|
375
|
+
['', '3c', '3c'], ['', '4e', '4e'], ['', '3e', '3e'], ['', 'line', 'line'],
|
|
376
|
+
['', 'L', 'line'], ['', 'L', 'L'], ['', '1e', '1e'],
|
|
377
|
+
['good', 'pair', 'pair'], ['bad', 'pair', 'pair'],
|
|
378
|
+
['good', 'arrow', 'pair'], ['bad', 'arrow', 'pair'],
|
|
379
|
+
['good', 'arrow', 'arrow'], ['bad', 'arrow', 'arrow'],
|
|
380
|
+
['', 'gem', 'gem'], ['', 'gem', 'knight'], ['', 'gem', 'axe'], ['', 'gem', 'squid'],
|
|
381
|
+
['good', 'knight', 'knight'], ['bad', 'knight', 'knight'],
|
|
382
|
+
['good', 'knight', 'axe'], ['bad', 'knight', 'axe'],
|
|
383
|
+
['same', 'axe', 'axe'], ['diff', 'axe', 'axe'],
|
|
384
|
+
['', 'squid', 'knight'], ['', 'squid', 'axe'], ['', 'squid', 'squid'],
|
|
385
|
+
['good', 'thumb', 'thumb'], ['bad', 'thumb', 'thumb'],
|
|
386
|
+
['good', 'thumb', 'bunny'], ['bad', 'thumb', 'bunny'],
|
|
387
|
+
['good', 'bunny', 'bunny'], ['bad', 'bunny', 'bunny'],
|
|
388
|
+
['', 'shell', 'shell'], ['', 'shell', 'bird'], ['', 'shell', 'hazard'],
|
|
389
|
+
['', 'yoshi', 'shell'],
|
|
390
|
+
['good', 'bird', 'bird'], ['bad', 'bird', 'bird'],
|
|
391
|
+
['', 'bird', 'hazard'], ['', 'hazard', 'hazard'],
|
|
392
|
+
['good', 'yoshi', 'bird'], ['bad', 'yoshi', 'bird'],
|
|
393
|
+
['', 'yoshi', 'hazard'], ['same', 'yoshi', 'yoshi'], ['diff', 'yoshi', 'yoshi'],
|
|
394
|
+
['good', 'kite', 'kite'], ['bad', 'kite', 'kite'],
|
|
395
|
+
['good', 'kite', 'cut'], ['bad', 'kite', 'cut'],
|
|
396
|
+
['', 'kite', 'T'], ['good', 'kite', 'N'], ['bad', 'kite', 'N'], ['', 'kite', 'tie'],
|
|
397
|
+
['', 'cut', 'T'], ['good', 'cut', 'N'], ['bad', 'cut', 'N'], ['', 'cut', 'tie'],
|
|
398
|
+
['good', 'cut', 'cut'], ['bad', 'cut', 'cut'],
|
|
399
|
+
['good', 'T', 'T'], ['bad', 'T', 'T'], ['', 'T', 'N'],
|
|
400
|
+
['good', 'T', 'tie'], ['bad', 'T', 'tie'],
|
|
401
|
+
['good', 'N', 'N'], ['bad', 'N', 'N'], ['', 'tie', 'N'],
|
|
402
|
+
['good', 'tie', 'tie'], ['bad', 'tie', 'tie']
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* OBL_LEN: optimal slicecount of each OBL
|
|
407
|
+
*/
|
|
408
|
+
static OBL_LEN = {
|
|
409
|
+
"solved/solved": 0, "1c/1c": 5, "cadj/cadj": 4, "cadj/copp": 5, "copp/copp": 2,
|
|
410
|
+
"3c/3c": 5, "4e/4e": 4, "3e/3e": 5, "line/line": 2, "L/line": 5, "L/L": 4, "1e/1e": 5,
|
|
411
|
+
"good pair/pair": 2, "bad pair/pair": 4, "good arrow/pair": 3, "bad arrow/pair": 4,
|
|
412
|
+
"good arrow/arrow": 3, "bad arrow/arrow": 4, "gem/gem": 4, "gem/knight": 4,
|
|
413
|
+
"gem/axe": 3, "gem/squid": 4, "good knight/knight": 4, "bad knight/knight": 5,
|
|
414
|
+
"good knight/axe": 3, "bad knight/axe": 4, "same axe/axe": 5, "diff axe/axe": 5,
|
|
415
|
+
"squid/knight": 4, "squid/axe": 4, "squid/squid": 5, "good thumb/thumb": 2,
|
|
416
|
+
"bad thumb/thumb": 5, "good thumb/bunny": 4, "bad thumb/bunny": 4,
|
|
417
|
+
"good bunny/bunny": 3, "bad bunny/bunny": 5, "shell/shell": 4, "shell/bird": 4,
|
|
418
|
+
"shell/hazard": 4, "yoshi/shell": 3, "good bird/bird": 4, "bad bird/bird": 5,
|
|
419
|
+
"bird/hazard": 4, "hazard/hazard": 5, "good yoshi/bird": 3, "bad yoshi/bird": 4,
|
|
420
|
+
"yoshi/hazard": 4, "same yoshi/yoshi": 5, "diff yoshi/yoshi": 5,
|
|
421
|
+
"good kite/kite": 1, "bad kite/kite": 5, "good kite/cut": 3, "bad kite/cut": 6,
|
|
422
|
+
"kite/T": 4, "good kite/N": 3, "bad kite/N": 4, "kite/tie": 4, "cut/T": 4,
|
|
423
|
+
"good cut/N": 4, "bad cut/N": 5, "cut/tie": 4, "good cut/cut": 3, "bad cut/cut": 6,
|
|
424
|
+
"good T/T": 3, "bad T/T": 4, "T/N": 5, "good T/tie": 3, "bad T/tie": 4,
|
|
425
|
+
"good N/N": 2, "bad N/N": 4, "tie/N": 5, "good tie/tie": 3, "bad tie/tie": 4
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* OBL_TRANSLATION: map nonspecific OBL to specific OBLs without layer flips
|
|
430
|
+
*/
|
|
431
|
+
static OBL_TRANSLATION = {
|
|
432
|
+
'solved/solved': ['solved/solved'],
|
|
433
|
+
'1c/1c': ['1c/1c'],
|
|
434
|
+
'cadj/cadj': ['cadj/cadj'],
|
|
435
|
+
'cadj/copp': ['cadj/copp'],
|
|
436
|
+
'copp/copp': ['copp/copp'],
|
|
437
|
+
'3c/3c': ['3c/3c'],
|
|
438
|
+
'4e/4e': ['4e/4e'],
|
|
439
|
+
'3e/3e': ['3e/3e'],
|
|
440
|
+
'line/line': ['line/line'],
|
|
441
|
+
'L/line': ['L/line'],
|
|
442
|
+
'L/L': ['L/L'],
|
|
443
|
+
'1e/1e': ['1e/1e'],
|
|
444
|
+
'good pair/pair': ['left pair/left pair', 'right pair/right pair'],
|
|
445
|
+
'bad pair/pair': ['left pair/right pair'],
|
|
446
|
+
'good arrow/pair': ['left arrow/right pair', 'right arrow/left pair'],
|
|
447
|
+
'bad arrow/pair': ['left arrow/left pair', 'right arrow/right pair'],
|
|
448
|
+
'good arrow/arrow': ['left arrow/left arrow', 'right arrow/right arrow'],
|
|
449
|
+
'bad arrow/arrow': ['left arrow/right arrow'],
|
|
450
|
+
'gem/gem': ['gem/gem'],
|
|
451
|
+
'gem/knight': ['gem/left knight', 'gem/right knight'],
|
|
452
|
+
'gem/axe': ['gem/left axe', 'gem/right axe'],
|
|
453
|
+
'gem/squid': ['gem/squid'],
|
|
454
|
+
'good knight/knight': ['left knight/right knight'],
|
|
455
|
+
'bad knight/knight': ['left knight/left knight', 'right knight/right knight'],
|
|
456
|
+
'good knight/axe': ['left knight/left axe', 'right knight/right axe'],
|
|
457
|
+
'bad knight/axe': ['left knight/right axe', 'right knight/left axe'],
|
|
458
|
+
'same axe/axe': ['left axe/left axe', 'right axe/right axe'],
|
|
459
|
+
'diff axe/axe': ['left axe/right axe'],
|
|
460
|
+
'squid/knight': ['squid/left knight', 'squid/right knight'],
|
|
461
|
+
'squid/axe': ['squid/left axe', 'squid/right axe'],
|
|
462
|
+
'squid/squid': ['squid/squid'],
|
|
463
|
+
'good thumb/thumb': ['left thumb/left thumb', 'right thumb/right thumb'],
|
|
464
|
+
'bad thumb/thumb': ['left thumb/right thumb'],
|
|
465
|
+
'good thumb/bunny': ['left thumb/right bunny', 'right thumb/left bunny'],
|
|
466
|
+
'bad thumb/bunny': ['left thumb/left bunny', 'right thumb/right bunny'],
|
|
467
|
+
'good bunny/bunny': ['left bunny/left bunny', 'right bunny/right bunny'],
|
|
468
|
+
'bad bunny/bunny': ['left bunny/right bunny'],
|
|
469
|
+
'shell/shell': ['shell/shell'],
|
|
470
|
+
'shell/bird': ['shell/left bird', 'shell/right bird'],
|
|
471
|
+
'shell/hazard': ['shell/hazard'],
|
|
472
|
+
'yoshi/shell': ['left yoshi/shell', 'right yoshi/shell'],
|
|
473
|
+
'good bird/bird': ['left bird/right bird'],
|
|
474
|
+
'bad bird/bird': ['left bird/left bird', 'right bird/right bird'],
|
|
475
|
+
'bird/hazard': ['left bird/hazard', 'right bird/hazard'],
|
|
476
|
+
'hazard/hazard': ['hazard/hazard'],
|
|
477
|
+
'good yoshi/bird': ['left yoshi/left bird', 'right yoshi/right bird'],
|
|
478
|
+
'bad yoshi/bird': ['left yoshi/right bird', 'right yoshi/left bird'],
|
|
479
|
+
'yoshi/hazard': ['left yoshi/hazard', 'right yoshi/hazard'],
|
|
480
|
+
'same yoshi/yoshi': ['left yoshi/left yoshi', 'right yoshi/right yoshi'],
|
|
481
|
+
'diff yoshi/yoshi': ['left yoshi/right yoshi'],
|
|
482
|
+
'good kite/kite': ['left kite/left kite', 'right kite/right kite'],
|
|
483
|
+
'bad kite/kite': ['left kite/right kite'],
|
|
484
|
+
'good kite/cut': ['left kite/left cut', 'right kite/right cut'],
|
|
485
|
+
'bad kite/cut': ['left kite/right cut', 'right kite/left cut'],
|
|
486
|
+
'kite/T': ['left kite/black T', 'left kite/white T', 'right kite/black T', 'right kite/white T'],
|
|
487
|
+
'good kite/N': ['left kite/right N', 'right kite/left N'],
|
|
488
|
+
'bad kite/N': ['left kite/left N', 'right kite/right N'],
|
|
489
|
+
'kite/tie': ['left kite/black tie', 'left kite/white tie', 'right kite/black tie', 'right kite/white tie'],
|
|
490
|
+
'cut/T': ['left cut/black T', 'left cut/white T', 'right cut/black T', 'right cut/white T'],
|
|
491
|
+
'good cut/N': ['left cut/left N', 'right cut/right N'],
|
|
492
|
+
'bad cut/N': ['left cut/right N', 'right cut/left N'],
|
|
493
|
+
'cut/tie': ['left cut/black tie', 'left cut/white tie', 'right cut/black tie', 'right cut/white tie'],
|
|
494
|
+
'good cut/cut': ['left cut/left cut', 'right cut/right cut'],
|
|
495
|
+
'bad cut/cut': ['left cut/right cut'],
|
|
496
|
+
'good T/T': ['black T/black T', 'white T/white T'],
|
|
497
|
+
'bad T/T': ['black T/white T'],
|
|
498
|
+
'T/N': ['black T/left N', 'black T/right N', 'white T/left N', 'white T/right N'],
|
|
499
|
+
'good T/tie': ['black T/black tie', 'white T/white tie'],
|
|
500
|
+
'bad T/tie': ['black T/white tie', 'white T/black tie'],
|
|
501
|
+
'good N/N': ['left N/left N', 'right N/right N'],
|
|
502
|
+
'bad N/N': ['left N/right N'],
|
|
503
|
+
'tie/N': ['black tie/left N', 'black tie/right N', 'white tie/left N', 'white tie/right N'],
|
|
504
|
+
'good tie/tie': ['black tie/black tie', 'white tie/white tie'],
|
|
505
|
+
'bad tie/tie': ['black tie/white tie']
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* CORNERS: possible OBLP corner memo
|
|
510
|
+
*/
|
|
511
|
+
static CORNERS = [[''], ['1', '3', '5', '7'], ['13', '15', '17', '35', '37', '57'], ['135', '137', '157', '357'], ['1357']];
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* EDGES: possible OBLP edge memo
|
|
515
|
+
*/
|
|
516
|
+
static EDGES = [[''], ['2', '4', '6', '8'], ['24', '26', '28', '46', '48', '68'], ['246', '248', '268', '468'], ['2468']];
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* TOTAL_CORNERS: flat list of CORNERS
|
|
520
|
+
*/
|
|
521
|
+
static TOTAL_CORNERS = ['', '1', '3', '5', '7', '13', '15', '17', '35', '37', '57', '135', '137', '157', '357', '1357'];
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* TOTAL_EDGES: flat list of EDGES
|
|
525
|
+
*/
|
|
526
|
+
static TOTAL_EDGES = ['', '2', '4', '6', '8', '24', '26', '28', '46', '48', '68', '246', '248', '268', '468', '2468'];
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* @param {object} [tempReplacements]: initial manual unkarnifications.
|
|
271
530
|
*/
|
|
272
531
|
constructor(tempReplacements = { "meow :3": "meow :3" }) {
|
|
273
532
|
// place to put manual unkarnifications
|
|
@@ -275,9 +534,9 @@ export default class SquanLib {
|
|
|
275
534
|
}
|
|
276
535
|
|
|
277
536
|
/**
|
|
278
|
-
* setTempReplacements
|
|
537
|
+
* setTempReplacements: replace the entire tempReplacements map.
|
|
279
538
|
*
|
|
280
|
-
* @param {Object<string,string>} replacements
|
|
539
|
+
* @param {Object<string,string>} replacements the new key→value pairs
|
|
281
540
|
* @returns {this}
|
|
282
541
|
*/
|
|
283
542
|
setTempReplacements(replacements) {
|
|
@@ -286,9 +545,9 @@ export default class SquanLib {
|
|
|
286
545
|
}
|
|
287
546
|
|
|
288
547
|
/**
|
|
289
|
-
* addTempReplacements
|
|
548
|
+
* addTempReplacements: merge key→value pairs into tempReplacements.
|
|
290
549
|
*
|
|
291
|
-
* @param {Object<string,string>} replacements
|
|
550
|
+
* @param {Object<string,string>} replacements: pairs to add (overwrites collisions)
|
|
292
551
|
* @returns {this}
|
|
293
552
|
*/
|
|
294
553
|
addTempReplacements(replacements) {
|
|
@@ -298,16 +557,16 @@ export default class SquanLib {
|
|
|
298
557
|
|
|
299
558
|
|
|
300
559
|
// =========================================================================
|
|
301
|
-
// SECTION 2
|
|
560
|
+
// SECTION 2: CORE UTILITIES
|
|
302
561
|
// =========================================================================
|
|
303
562
|
|
|
304
563
|
/**
|
|
305
|
-
* dictReplace
|
|
564
|
+
* dictReplace: repeatedly applies every key→value substitution in `dict`
|
|
306
565
|
* to `str` until the string stabilizes.
|
|
307
566
|
*
|
|
308
|
-
* @param {string} str
|
|
309
|
-
* @param {object} dict
|
|
310
|
-
* @returns {string}
|
|
567
|
+
* @param {string} str the string to be replaced
|
|
568
|
+
* @param {object} dict the dictionary
|
|
569
|
+
* @returns {string} the fully replaced string
|
|
311
570
|
*/
|
|
312
571
|
dictReplace(str, dict) {
|
|
313
572
|
const pattern = new RegExp(
|
|
@@ -320,7 +579,7 @@ export default class SquanLib {
|
|
|
320
579
|
}
|
|
321
580
|
|
|
322
581
|
/**
|
|
323
|
-
* addCommas
|
|
582
|
+
* addCommas: e.g. "2-1" → "2,-1"
|
|
324
583
|
*
|
|
325
584
|
* length 1 → "N,0"
|
|
326
585
|
* length 2 → starts with '-'? "-N,0" : "A,B"
|
|
@@ -328,11 +587,11 @@ export default class SquanLib {
|
|
|
328
587
|
* length 4 → "AB,CD"
|
|
329
588
|
* anything else that is not all-digits/minus → pass through unchanged
|
|
330
589
|
*
|
|
331
|
-
* @param {string} alg
|
|
332
|
-
* @returns {string}
|
|
590
|
+
* @param {string} alg the scramble, any separator (no additional spaces). can have commas already.
|
|
591
|
+
* @returns {string} the scramble, with commas added
|
|
333
592
|
*/
|
|
334
593
|
addCommas(alg) {
|
|
335
|
-
return alg.split(
|
|
594
|
+
return alg.split(/[/\\| ]/).map(move => {
|
|
336
595
|
if (!move || isNaN(Number(move.replaceAll('-', ''))) || move.includes(","))
|
|
337
596
|
return move;
|
|
338
597
|
switch (move.length) {
|
|
@@ -348,36 +607,50 @@ export default class SquanLib {
|
|
|
348
607
|
}
|
|
349
608
|
|
|
350
609
|
/**
|
|
351
|
-
* isKarn
|
|
610
|
+
* isKarn: returns true if the string uses any letters
|
|
352
611
|
*
|
|
353
|
-
* @param {string} str
|
|
354
|
-
* @returns {boolean}
|
|
612
|
+
* @param {string} str the alg
|
|
613
|
+
* @returns {boolean} whether the alg contains letters
|
|
355
614
|
*/
|
|
356
615
|
isKarn(str) {
|
|
357
616
|
return /[a-zA-Z]/.test(str);
|
|
358
617
|
}
|
|
359
618
|
|
|
360
619
|
/**
|
|
361
|
-
*
|
|
620
|
+
* getAlignmentMove: turns topA and bottomA into a starting move
|
|
362
621
|
*
|
|
363
|
-
* @param {boolean} topA
|
|
364
|
-
* @param {boolean} bottomA
|
|
365
|
-
* @returns {string}
|
|
622
|
+
* @param {boolean} topA top misalign?
|
|
623
|
+
* @param {boolean} bottomA bottom misalign?
|
|
624
|
+
* @returns {string} e.g. "10", "1-1"
|
|
366
625
|
*/
|
|
367
|
-
|
|
626
|
+
getAlignmentMove(topA, bottomA) {
|
|
368
627
|
return (topA ? '1' : '0') + (bottomA ? '-1' : '0');
|
|
369
628
|
}
|
|
370
629
|
|
|
630
|
+
/**
|
|
631
|
+
* getAlignment: turns a starting/ending move into topA and bottomA
|
|
632
|
+
*
|
|
633
|
+
* @param {string} m the move
|
|
634
|
+
* @returns {topA: top misalign?, bottomA: bottom misalign?}}
|
|
635
|
+
*/
|
|
636
|
+
getAlignment(m) {
|
|
637
|
+
if (!m) return { topA: false, bottomA: false } // this is just a 00 move
|
|
638
|
+
m = this.addCommas(m);
|
|
639
|
+
if (!m.includes(",")) throw new Error("getAlignment: move is weird: " + m);
|
|
640
|
+
const [u, d] = m.split(",");
|
|
641
|
+
return { topA: u !== "0", bottomA: d !== "0" }
|
|
642
|
+
}
|
|
643
|
+
|
|
371
644
|
|
|
372
645
|
// =========================================================================
|
|
373
|
-
// SECTION 3
|
|
646
|
+
// SECTION 3: UNKARNIFY PIPELINE
|
|
374
647
|
// =========================================================================
|
|
375
648
|
|
|
376
649
|
/**
|
|
377
|
-
* unkarnifyHelp
|
|
650
|
+
* unkarnifyHelp: does the actual unkarnifying
|
|
378
651
|
*
|
|
379
|
-
* @param {string} alg
|
|
380
|
-
* @returns {string}
|
|
652
|
+
* @param {string} alg the alg
|
|
653
|
+
* @returns {string} surface-level unkarnified alg
|
|
381
654
|
*/
|
|
382
655
|
unkarnifyHelp(alg) {
|
|
383
656
|
// trim and replace random ass characters
|
|
@@ -427,11 +700,11 @@ export default class SquanLib {
|
|
|
427
700
|
}
|
|
428
701
|
|
|
429
702
|
/**
|
|
430
|
-
* unkarnify
|
|
703
|
+
* unkarnify: master karn → WCA
|
|
431
704
|
* basically unkarnifyHelp + replaceShorthand with bling blings
|
|
432
705
|
*
|
|
433
|
-
* @param {string} alg
|
|
434
|
-
* @returns {string}
|
|
706
|
+
* @param {string} alg the alg to be unkarnified
|
|
707
|
+
* @returns {string} unkarnified alg, duh
|
|
435
708
|
*/
|
|
436
709
|
unkarnify(alg) {
|
|
437
710
|
// overrides
|
|
@@ -474,11 +747,11 @@ export default class SquanLib {
|
|
|
474
747
|
}
|
|
475
748
|
|
|
476
749
|
/**
|
|
477
|
-
* replaceShorthands
|
|
750
|
+
* replaceShorthands: replace shorthands (bjj, fv, kk, …) in an alg,
|
|
478
751
|
* tracking alignment state to choose the correct shorthand.
|
|
479
752
|
*
|
|
480
|
-
* @param {string} alg
|
|
481
|
-
* @returns {string}
|
|
753
|
+
* @param {string} alg the alg
|
|
754
|
+
* @returns {string} the alg with shorthands replaced... guys jsdoc is sometimes dumb
|
|
482
755
|
*/
|
|
483
756
|
replaceShorthands(alg) {
|
|
484
757
|
const moves = alg.split(/[\/\\\|]/);
|
|
@@ -495,7 +768,7 @@ export default class SquanLib {
|
|
|
495
768
|
if (!move) continue;
|
|
496
769
|
|
|
497
770
|
if (move.includes(',')) {
|
|
498
|
-
// Numeric turn
|
|
771
|
+
// Numeric turn: update alignment tracker.
|
|
499
772
|
const [u, d] = move.split(',');
|
|
500
773
|
if (parseInt(u, 10) % 3 !== 0) topA = !topA;
|
|
501
774
|
if (parseInt(d, 10) % 3 !== 0) bottomA = !bottomA;
|
|
@@ -503,11 +776,11 @@ export default class SquanLib {
|
|
|
503
776
|
// shorthand
|
|
504
777
|
const key = SquanLib.alignmentIndependent.has(move.toLowerCase())
|
|
505
778
|
? move.toLowerCase()
|
|
506
|
-
: move.toLowerCase() + this.
|
|
779
|
+
: move.toLowerCase() + this.getAlignmentMove(topA, bottomA);
|
|
507
780
|
|
|
508
781
|
const replacement = SquanLib.shorthandToKarn[key];
|
|
509
782
|
if (replacement === undefined)
|
|
510
|
-
throw new Error(`replaceShorthands: "${move}" with alignment ${this.
|
|
783
|
+
throw new Error(`replaceShorthands: "${move}" with alignment ${this.getAlignmentMove(topA, bottomA)} is not defined.`);
|
|
511
784
|
|
|
512
785
|
alg = alg.replace(move, replacement);
|
|
513
786
|
|
|
@@ -527,11 +800,11 @@ export default class SquanLib {
|
|
|
527
800
|
|
|
528
801
|
|
|
529
802
|
// =========================================================================
|
|
530
|
-
// SECTION 4
|
|
803
|
+
// SECTION 4: SCRAMBLE / ALG UTILITIES
|
|
531
804
|
// =========================================================================
|
|
532
805
|
|
|
533
806
|
/**
|
|
534
|
-
* parseScramble
|
|
807
|
+
* parseScramble: tokenizes a WCA squan scramble
|
|
535
808
|
*
|
|
536
809
|
* @param {string} alg the alg
|
|
537
810
|
* @returns {{ type: string, top?: number, bottom?: number }[]} after parsing
|
|
@@ -556,7 +829,7 @@ export default class SquanLib {
|
|
|
556
829
|
}
|
|
557
830
|
|
|
558
831
|
/**
|
|
559
|
-
* twist
|
|
832
|
+
* twist: does a slice on a hex string
|
|
560
833
|
*
|
|
561
834
|
* @param {string} tlHex top layer hex, from UFL clockwise
|
|
562
835
|
* @param {string} blHex bottom layer hex, from DF clockwise
|
|
@@ -569,19 +842,30 @@ export default class SquanLib {
|
|
|
569
842
|
}
|
|
570
843
|
|
|
571
844
|
/**
|
|
572
|
-
*
|
|
845
|
+
* doSlice: does a slice on a CSP-style OBL cube. throws error if unsliceable.
|
|
573
846
|
*
|
|
574
|
-
* @param {string}
|
|
575
|
-
* @
|
|
576
|
-
* @returns {string} the shifted string
|
|
847
|
+
* @param {string} cube the CSP-style "BbWw" cube
|
|
848
|
+
* @returns {string} the cube post-slice, if sliceable
|
|
577
849
|
*/
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
850
|
+
doSlice(cube) {
|
|
851
|
+
const [UR, UL, DR, DL] = [
|
|
852
|
+
cube.slice(0, SquanLib.HALF_L),
|
|
853
|
+
cube.slice(SquanLib.HALF_L, SquanLib.LAYERL),
|
|
854
|
+
cube.slice(SquanLib.LAYERL, SquanLib.THREE_FOUR_L),
|
|
855
|
+
cube.slice(SquanLib.THREE_FOUR_L, SquanLib.CUBEL)
|
|
856
|
+
];
|
|
857
|
+
const isUP = (char) => char === char.toUpperCase();
|
|
858
|
+
const canSlice = (halfLayer) => (
|
|
859
|
+
!isUP(halfLayer.at(0)) || isUP(halfLayer.at(1)) &&
|
|
860
|
+
!isUP(halfLayer.at(-1)) || isUP(halfLayer.at(-2))
|
|
861
|
+
);
|
|
862
|
+
if (!([UR, UL, DR, DL].map(canSlice).every(Boolean)))
|
|
863
|
+
throw new Error("doSlice: unsliceable position encountered");
|
|
864
|
+
return DR + UL + UR + DL;
|
|
581
865
|
}
|
|
582
866
|
|
|
583
867
|
/**
|
|
584
|
-
* algToHex
|
|
868
|
+
* algToHex: get the hex state that the alg generates
|
|
585
869
|
*
|
|
586
870
|
* @param {string} alg an alg. karn is accepted.
|
|
587
871
|
* @returns {tlHex: string, blHex: string} the hex
|
|
@@ -593,15 +877,48 @@ export default class SquanLib {
|
|
|
593
877
|
if (move.type === 'twist') {
|
|
594
878
|
({ tlHex, blHex } = this.twist(tlHex, blHex));
|
|
595
879
|
} else {
|
|
596
|
-
tlHex = this.
|
|
597
|
-
blHex = this.
|
|
880
|
+
tlHex = this.shift(tlHex, -move.top);
|
|
881
|
+
blHex = this.shift(blHex, -move.bottom);
|
|
598
882
|
}
|
|
599
883
|
}
|
|
600
884
|
return { tlHex, blHex };
|
|
601
885
|
}
|
|
602
886
|
|
|
603
887
|
/**
|
|
604
|
-
*
|
|
888
|
+
* doMoves: does moves on a cube
|
|
889
|
+
*
|
|
890
|
+
* @param {string} ms the moves. karn accepted.
|
|
891
|
+
* @param {string} s the starting CSP-styled cube. leave to use the solved OBL state
|
|
892
|
+
* @returns {string} the cube post-moves
|
|
893
|
+
*/
|
|
894
|
+
doMoves(ms, s) {
|
|
895
|
+
if (s === undefined) s = SquanLib.SOLVED;
|
|
896
|
+
for (const tok of this.unkarnify(ms).split('/')) {
|
|
897
|
+
const m = tok.trim();
|
|
898
|
+
if (m !== '') {
|
|
899
|
+
const [u, d] = m.split(',').map(Number);
|
|
900
|
+
s = this.moveCube(s, u, d);
|
|
901
|
+
}
|
|
902
|
+
s = this.doSlice(s);
|
|
903
|
+
}
|
|
904
|
+
return this.doSlice(s);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* moveCube: does a move on a CSP-style cube
|
|
909
|
+
*
|
|
910
|
+
* @param {string} cube the CSP-style cube
|
|
911
|
+
* @param {number} u the U move
|
|
912
|
+
* @param {number} d the D move
|
|
913
|
+
* @returns {string} the cube post-move
|
|
914
|
+
*/
|
|
915
|
+
moveCube(cube, u, d) {
|
|
916
|
+
return this.shift(cube.slice(0, SquanLib.LAYERL), u) +
|
|
917
|
+
this.shift(cube.slice(SquanLib.LAYERL), d);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* invertScramble: reverses a scramble
|
|
605
922
|
*
|
|
606
923
|
* @param {string} alg the alg. karn is accepted.
|
|
607
924
|
* @returns {string} the reversed alg
|
|
@@ -623,7 +940,7 @@ export default class SquanLib {
|
|
|
623
940
|
}
|
|
624
941
|
|
|
625
942
|
/**
|
|
626
|
-
* isPBL
|
|
943
|
+
* isPBL: check if a hex is PBL
|
|
627
944
|
*
|
|
628
945
|
* @param {string} hex
|
|
629
946
|
* @returns {boolean} whether it's a PBL
|
|
@@ -682,11 +999,11 @@ export default class SquanLib {
|
|
|
682
999
|
|
|
683
1000
|
|
|
684
1001
|
// =========================================================================
|
|
685
|
-
// SECTION 5
|
|
1002
|
+
// SECTION 5: KARNIFY (WCA → karn)
|
|
686
1003
|
// =========================================================================
|
|
687
1004
|
|
|
688
1005
|
/**
|
|
689
|
-
* karnify
|
|
1006
|
+
* karnify: converts WCA to karn.
|
|
690
1007
|
*
|
|
691
1008
|
* 1. assert that no two slices are next to each other and it's fully numeric
|
|
692
1009
|
* 2. compute any startingSlice and endingSlice
|
|
@@ -696,8 +1013,8 @@ export default class SquanLib {
|
|
|
696
1013
|
* 5. join back into an alg while reattaching leading/trailing slices,
|
|
697
1014
|
* and dictReplace for high karns
|
|
698
1015
|
*
|
|
699
|
-
* @param {string} alg
|
|
700
|
-
* @returns {string}
|
|
1016
|
+
* @param {string} alg WCA format
|
|
1017
|
+
* @returns {string} karn
|
|
701
1018
|
*/
|
|
702
1019
|
karnify(alg) {
|
|
703
1020
|
alg = alg.trim();
|
|
@@ -727,14 +1044,14 @@ export default class SquanLib {
|
|
|
727
1044
|
|
|
728
1045
|
|
|
729
1046
|
// =========================================================================
|
|
730
|
-
// SECTION 6
|
|
1047
|
+
// SECTION 6: MOVE MATH & SEQUENCE OPTIMIZER
|
|
731
1048
|
// =========================================================================
|
|
732
1049
|
|
|
733
1050
|
/**
|
|
734
|
-
* legalMove
|
|
1051
|
+
* legalMove: normalizes a raw turn value into the canonical squan range [−5, 6].
|
|
735
1052
|
*
|
|
736
|
-
* @param {number} m
|
|
737
|
-
* @returns {number}
|
|
1053
|
+
* @param {number} m raw turn value, e.g. −7 or 9
|
|
1054
|
+
* @returns {number} normalized value in [−5, 6]
|
|
738
1055
|
*/
|
|
739
1056
|
legalMove(m) {
|
|
740
1057
|
m = m % 12; // get a range from -11 to 11
|
|
@@ -744,7 +1061,7 @@ export default class SquanLib {
|
|
|
744
1061
|
}
|
|
745
1062
|
|
|
746
1063
|
/**
|
|
747
|
-
* addMoves
|
|
1064
|
+
* addMoves: adds two move strings component-wise and legalizes each result.
|
|
748
1065
|
*
|
|
749
1066
|
* Tolerates "A"/"a" alignment markers. When one operand is an alignment
|
|
750
1067
|
* marker and the other is a move, the marker is flipped iff the top
|
|
@@ -752,8 +1069,8 @@ export default class SquanLib {
|
|
|
752
1069
|
* The above assumes that the move is in-CS.
|
|
753
1070
|
* Both operands cannot simultaneously be alignment markers.
|
|
754
1071
|
*
|
|
755
|
-
* @param {string} move1
|
|
756
|
-
* @param {string} move2
|
|
1072
|
+
* @param {string} move1 "top,bot" string, OR "A"/"a" alignment marker
|
|
1073
|
+
* @param {string} move2 "top,bot" string, OR "A"/"a" alignment marker
|
|
757
1074
|
* @returns {string}
|
|
758
1075
|
*
|
|
759
1076
|
* @example
|
|
@@ -783,11 +1100,11 @@ export default class SquanLib {
|
|
|
783
1100
|
}
|
|
784
1101
|
|
|
785
1102
|
/**
|
|
786
|
-
* changesAlignment
|
|
1103
|
+
* changesAlignment: check if performing this turn value changes the
|
|
787
1104
|
* alignment state (i.e. the turn is not a multiple of 3). Assumes everything
|
|
788
1105
|
* is in CS.
|
|
789
1106
|
*
|
|
790
|
-
* @param {number} m
|
|
1107
|
+
* @param {number} m top-layer turn value,
|
|
791
1108
|
* @returns {boolean}
|
|
792
1109
|
*/
|
|
793
1110
|
changesAlignment(m) {
|
|
@@ -795,7 +1112,7 @@ export default class SquanLib {
|
|
|
795
1112
|
}
|
|
796
1113
|
|
|
797
1114
|
/**
|
|
798
|
-
* optimize
|
|
1115
|
+
* optimize: replaces known optimizable moves (the OPTIM table) in a WCA alg
|
|
799
1116
|
*
|
|
800
1117
|
* 1. if no more optimization can be done, exit.
|
|
801
1118
|
* 2. split on "/" into an array
|
|
@@ -807,8 +1124,8 @@ export default class SquanLib {
|
|
|
807
1124
|
* the last into the succeeding move, and replace the inners
|
|
808
1125
|
* 6. restart the outer loop after any change
|
|
809
1126
|
*
|
|
810
|
-
* @param {string} alg
|
|
811
|
-
* @returns {string}
|
|
1127
|
+
* @param {string} alg slash-separated WCA alg, e.g. "A/-3,0/3,3/3,3/a"
|
|
1128
|
+
* @returns {string} optimized alg
|
|
812
1129
|
*/
|
|
813
1130
|
optimize(alg) {
|
|
814
1131
|
const optimKeys = Object.keys(SquanLib.OPTIM);
|
|
@@ -856,14 +1173,14 @@ export default class SquanLib {
|
|
|
856
1173
|
|
|
857
1174
|
|
|
858
1175
|
// =========================================================================
|
|
859
|
-
// SECTION 7
|
|
1176
|
+
// SECTION 7: ERGONOMICS RATING
|
|
860
1177
|
// =========================================================================
|
|
861
1178
|
|
|
862
1179
|
/**
|
|
863
|
-
* getMoveValue
|
|
864
|
-
* @param {boolean} startA
|
|
865
|
-
* @param {boolean} upslice
|
|
866
|
-
* @param {string} move
|
|
1180
|
+
* getMoveValue: look up the ergonomic cost of a single move.
|
|
1181
|
+
* @param {boolean} startA is it top misalign right now?
|
|
1182
|
+
* @param {boolean} upslice is this an upslice?
|
|
1183
|
+
* @param {string} move e.g. "3,0"
|
|
867
1184
|
* @returns {number}
|
|
868
1185
|
*/
|
|
869
1186
|
getMoveValue(startA, upslice, move) {
|
|
@@ -884,8 +1201,8 @@ export default class SquanLib {
|
|
|
884
1201
|
}
|
|
885
1202
|
|
|
886
1203
|
/**
|
|
887
|
-
* getOverwork
|
|
888
|
-
* @param {string[]} moves
|
|
1204
|
+
* getOverwork: calculates how much overwork is present in an alg, along with bonus.
|
|
1205
|
+
* @param {string[]} moves array of "top,bot" strings (interior moves only)
|
|
889
1206
|
* @returns {{ movement: number, bonus: number }}
|
|
890
1207
|
*/
|
|
891
1208
|
getOverwork(moves) {
|
|
@@ -910,7 +1227,9 @@ export default class SquanLib {
|
|
|
910
1227
|
const isLeft = (t === 6 || t < 0);
|
|
911
1228
|
if (isLeft) {
|
|
912
1229
|
streak++;
|
|
913
|
-
|
|
1230
|
+
if (!SquanLib.CLOSEST_MAP.has(t))
|
|
1231
|
+
throw new Error("getOverwork: top move is weird: " + t)
|
|
1232
|
+
closestMov += Math.abs(SquanLib.CLOSEST_MAP.get(t));
|
|
914
1233
|
buffer += Math.abs(t);
|
|
915
1234
|
// has streak and nontrivial move → penalize
|
|
916
1235
|
if (streak > 1 && closestMov > 3) { movement += buffer; buffer = 0; }
|
|
@@ -923,7 +1242,9 @@ export default class SquanLib {
|
|
|
923
1242
|
const isLeft = b > 0;
|
|
924
1243
|
if (isLeft) {
|
|
925
1244
|
streak++;
|
|
926
|
-
|
|
1245
|
+
if (!SquanLib.CLOSEST_MAP.has(b))
|
|
1246
|
+
throw new Error("getOverwork: top move is weird: " + b)
|
|
1247
|
+
closestMov += Math.abs(SquanLib.CLOSEST_MAP.get(b));
|
|
927
1248
|
buffer += Math.abs(b);
|
|
928
1249
|
if (streak > 1 && closestMov > 3) { movement += buffer; buffer = 0; }
|
|
929
1250
|
} else { streak = 0; closestMov = 0; buffer = 0; }
|
|
@@ -939,11 +1260,11 @@ export default class SquanLib {
|
|
|
939
1260
|
}
|
|
940
1261
|
|
|
941
1262
|
/**
|
|
942
|
-
* rateAlg
|
|
1263
|
+
* rateAlg: ergonomic rating for a single in CS squan alg
|
|
943
1264
|
*
|
|
944
|
-
* @param {string} algRaw
|
|
945
|
-
* @param {boolean} initialTopA
|
|
946
|
-
* @param {object} [weights]
|
|
1265
|
+
* @param {string} algRaw raw alg (karn or WCA)
|
|
1266
|
+
* @param {boolean} initialTopA if the alg starts top misalign
|
|
1267
|
+
* @param {object} [weights] override default weight constants
|
|
947
1268
|
* @returns {{ score: number, sliceStart: string }}
|
|
948
1269
|
* sliceStart: '/' (prefer upslice), '\' (prefer downslice), or ' ' (no preference)
|
|
949
1270
|
*/
|
|
@@ -1009,10 +1330,10 @@ export default class SquanLib {
|
|
|
1009
1330
|
}
|
|
1010
1331
|
|
|
1011
1332
|
/**
|
|
1012
|
-
* rateAndSort
|
|
1333
|
+
* rateAndSort: rate a list of algs and return them sorted by ergonomics high to low
|
|
1013
1334
|
*
|
|
1014
|
-
* @param {string[]} algLines
|
|
1015
|
-
* @param {string} [posHex='']
|
|
1335
|
+
* @param {string[]} algLines raw alg strings
|
|
1336
|
+
* @param {string} [posHex=''] position hex (used to determine initialTopA)
|
|
1016
1337
|
* @returns {{ alg: string, score: number }[]}
|
|
1017
1338
|
*/
|
|
1018
1339
|
rateAndSort(algLines, posHex = '') {
|
|
@@ -1045,4 +1366,429 @@ export default class SquanLib {
|
|
|
1045
1366
|
return result;
|
|
1046
1367
|
}).sort((a, b) => b.score - a.score);
|
|
1047
1368
|
}
|
|
1369
|
+
|
|
1370
|
+
// =========================================================================
|
|
1371
|
+
// SECTION 8: ALG TRANSFORM
|
|
1372
|
+
// =========================================================================
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* sepIndex: helper to identify where the D move starts in a move without comma
|
|
1376
|
+
*
|
|
1377
|
+
* @param {string} a a move without commas, e.g. "3-3"
|
|
1378
|
+
* @returns {number} the 0-based index of the start of the D move
|
|
1379
|
+
* @example "3-3" → 1
|
|
1380
|
+
*/
|
|
1381
|
+
sepIndex(a) {
|
|
1382
|
+
let inx = 0;
|
|
1383
|
+
for (const ch of a) { inx++; if (/\d/.test(ch)) break; }
|
|
1384
|
+
return inx;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* compl: get the complement of a move
|
|
1389
|
+
*
|
|
1390
|
+
* @param {string} a a move without commas, e.g. "-12"
|
|
1391
|
+
* @returns {string} the complement move
|
|
1392
|
+
* @example "-12" → "5-4"
|
|
1393
|
+
*/
|
|
1394
|
+
compl(a) {
|
|
1395
|
+
if (!a) return a;
|
|
1396
|
+
const inx = this.sepIndex(a);
|
|
1397
|
+
return String(this.legalMove(6 + parseInt(a.slice(0, inx), 10))) +
|
|
1398
|
+
String(this.legalMove(6 + parseInt(a.slice(inx), 10)));
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* lf: get the layer flip of a move
|
|
1403
|
+
*
|
|
1404
|
+
* @param {string} a a move without commas, e.g. "-12"
|
|
1405
|
+
* @returns {string} the layer flip move
|
|
1406
|
+
* @example "-12" → "2-1"
|
|
1407
|
+
*/
|
|
1408
|
+
lf(a) {
|
|
1409
|
+
if (!a) return a;
|
|
1410
|
+
const inx = this.sepIndex(a);
|
|
1411
|
+
return a.slice(inx) + a.slice(0, inx);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* compact: parse any input format into space-separated no-comma compact
|
|
1416
|
+
* segments, e.g. "30 -33 30". uses full unkarnify for karn input.
|
|
1417
|
+
*
|
|
1418
|
+
* @param {string} algIn the inputted alg. any input format
|
|
1419
|
+
* @returns {string} the compact format
|
|
1420
|
+
*/
|
|
1421
|
+
compact(algIn) {
|
|
1422
|
+
let alg = algIn
|
|
1423
|
+
.replace(/\[.*?\]/g, "")
|
|
1424
|
+
.replace(/[()]/g, "")
|
|
1425
|
+
.replaceAll(" ", "").trim();
|
|
1426
|
+
if (this.isKarn(alg)) {
|
|
1427
|
+
const numeric = this.unkarnify(alg);
|
|
1428
|
+
return numeric.split("/").filter(Boolean).map(m => {
|
|
1429
|
+
if (!m.includes(","))
|
|
1430
|
+
throw new Error(
|
|
1431
|
+
`algToInternal: m doesn't have commas post karnifying: ${m}`
|
|
1432
|
+
)
|
|
1433
|
+
const [u, d] = m.split(",");
|
|
1434
|
+
return String(this.legalMove(parseInt(u, 10))) +
|
|
1435
|
+
String(this.legalMove(parseInt(d, 10)));
|
|
1436
|
+
}).join(" ");
|
|
1437
|
+
}
|
|
1438
|
+
alg = alg.trim();
|
|
1439
|
+
if (alg.includes("/")) {
|
|
1440
|
+
return alg.split("/").filter(Boolean).map(m => {
|
|
1441
|
+
if (!m.includes(",")) m = this.addCommas(m);
|
|
1442
|
+
if (!m.includes(","))
|
|
1443
|
+
throw new Error(
|
|
1444
|
+
`algToInternal: m doesn't have commas post addComma: ${m}`
|
|
1445
|
+
)
|
|
1446
|
+
const [u, d] = m.split(",");
|
|
1447
|
+
return String(this.legalMove(parseInt(u, 10))) +
|
|
1448
|
+
String(this.legalMove(parseInt(d, 10)));
|
|
1449
|
+
}).join(" ");
|
|
1450
|
+
}
|
|
1451
|
+
return alg.split(" ").filter(p => p).map(m => {
|
|
1452
|
+
const p = m.includes(",")
|
|
1453
|
+
? m.split(",")
|
|
1454
|
+
: [m.slice(0, this.sepIndex(m)), m.slice(this.sepIndex(m))];
|
|
1455
|
+
return String(this.legalMove(parseInt(p[0], 10))) +
|
|
1456
|
+
String(this.legalMove(parseInt(p[1], 10)));
|
|
1457
|
+
}).join(" ");
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* countY2Positions: count the number of possible y2 positions
|
|
1462
|
+
*
|
|
1463
|
+
* @param {string} algIn the alg
|
|
1464
|
+
* @returns {number} how many positions the alg can y2 at
|
|
1465
|
+
*/
|
|
1466
|
+
countY2Positions(algIn) {
|
|
1467
|
+
const segs = this.compact(algIn).split(" ").filter(p => p);
|
|
1468
|
+
return Math.max(0, segs.length - 3);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* applyY2s: y2 an alg at a list of specified slices, and give comments
|
|
1473
|
+
*
|
|
1474
|
+
* @param {string} algIn the alg
|
|
1475
|
+
* @param {number[]} lfLst the list of slice positions to y2 at
|
|
1476
|
+
* @param {boolean} k whether to output as karn, leave to use input format
|
|
1477
|
+
* @returns {string} the y2'ed alg, plus comments
|
|
1478
|
+
*/
|
|
1479
|
+
applyY2s(algIn, lfLst, k = null) {
|
|
1480
|
+
let alg = algIn.replaceAll(/\[.*?\]/g, "").trim();
|
|
1481
|
+
if (!lfLst) lfLst = [];
|
|
1482
|
+
|
|
1483
|
+
const ki = this.isKarn(alg);
|
|
1484
|
+
const kOut = k === null ? ki : k;
|
|
1485
|
+
|
|
1486
|
+
alg = this.unkarnify(algIn);
|
|
1487
|
+
let alst = alg.split("/");
|
|
1488
|
+
|
|
1489
|
+
// save for alignment-changes check
|
|
1490
|
+
const firstMove = alst[0];
|
|
1491
|
+
const lastMove = alst[alst.length - 1];
|
|
1492
|
+
|
|
1493
|
+
// Apply explicit y2s to interior moves
|
|
1494
|
+
let lfing = false, facingD = false;
|
|
1495
|
+
for (let i = 1; i <= alst.length - 3; i++) {
|
|
1496
|
+
let m = alst[i];
|
|
1497
|
+
m = facingD ? this.lf(m) : m;
|
|
1498
|
+
if (lfLst.includes(i)) { m = this.compl(m); lfing = !lfing; }
|
|
1499
|
+
facingD = lfing ? !facingD : facingD;
|
|
1500
|
+
alst[i] = m;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// fix last interior move
|
|
1504
|
+
const lastIntIdx = alst.length - 2;
|
|
1505
|
+
if (facingD) alst[lastIntIdx] = this.lf(alst[lastIntIdx]);
|
|
1506
|
+
if (lfing !== facingD) alst[lastIntIdx] = this.compl(alst[lastIntIdx]);
|
|
1507
|
+
const lastIntMove = alst[lastIntIdx];
|
|
1508
|
+
|
|
1509
|
+
// add commas to all moves
|
|
1510
|
+
for (let i = 0; i < alst.length; i++) alst[i] = this.addCommas(alst[i]);
|
|
1511
|
+
alg = alst.join('/');
|
|
1512
|
+
|
|
1513
|
+
// build comment
|
|
1514
|
+
let comment = "";
|
|
1515
|
+
if (lastIntMove && !SquanLib.GOOD_FINISHES.has(lastIntMove))
|
|
1516
|
+
comment += ' (bad finish)';
|
|
1517
|
+
const { topA: topAstart, bottomA: bottomAstart } = this.getAlignment(firstMove);
|
|
1518
|
+
const { topA: topAend, bottomA: bottomAend } = this.getAlignment(lastMove);
|
|
1519
|
+
if (topAstart !== bottomAstart &&
|
|
1520
|
+
topAstart !== topAend &&
|
|
1521
|
+
topAend !== bottomAend)
|
|
1522
|
+
comment += ' (alignment changes in CS)';
|
|
1523
|
+
|
|
1524
|
+
if (kOut) {
|
|
1525
|
+
alg = this.karnify(alg);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
return alg + comment;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// =========================================================================
|
|
1532
|
+
// SECTION 9: OBL and OBLP Utilities
|
|
1533
|
+
// =========================================================================
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* layerFlip: give the layer flipped state of a CSP-style OBL state
|
|
1537
|
+
*
|
|
1538
|
+
* @param {string} state an OBL state of however long
|
|
1539
|
+
* @returns {string} the layer flip of that
|
|
1540
|
+
*/
|
|
1541
|
+
layerFlip(state) {
|
|
1542
|
+
const layerFlipMap = { 'b': 'w', 'B': 'W', 'w': 'b', 'W': 'B' };
|
|
1543
|
+
return [...state].map(c => {
|
|
1544
|
+
if (c in layerFlipMap) return layerFlipMap[c];
|
|
1545
|
+
throw new Error("layerFlip: unrecognized character: " + c)
|
|
1546
|
+
}).join('');
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* shift: basically does a move on a layer
|
|
1551
|
+
*
|
|
1552
|
+
* @param {string} a CSP-style (layer) state
|
|
1553
|
+
* @param {number} amount the move. literally. cw = positive
|
|
1554
|
+
* @returns {string} the state after the move
|
|
1555
|
+
*/
|
|
1556
|
+
shift(a, amount) {
|
|
1557
|
+
amount = ((-amount % a.length) + a.length) % a.length;
|
|
1558
|
+
return a.slice(amount) + a.slice(0, amount);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* oblName: turns a possibleOBL array into a string
|
|
1563
|
+
*
|
|
1564
|
+
* @param {string[]} obl a possibleOBL-style array
|
|
1565
|
+
* @returns {string} the actual OBL name
|
|
1566
|
+
*/
|
|
1567
|
+
oblName(obl) {
|
|
1568
|
+
return obl[0] ? `${obl[0]} ${obl[1]}/${obl[2]}` : `${obl[1]}/${obl[2]}`;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* layerFlipName: layer flips an OBL name
|
|
1573
|
+
*
|
|
1574
|
+
* @param {string} obl an unspecific OBL name
|
|
1575
|
+
* @returns {string} the layer flipped version
|
|
1576
|
+
* @example "good bunny/thumb" → "good thumb/bunny"
|
|
1577
|
+
*/
|
|
1578
|
+
layerFlipName(obl) {
|
|
1579
|
+
obl = obl.replace('/', ' ');
|
|
1580
|
+
const parts = obl.split(' ');
|
|
1581
|
+
if (parts.length === 2) return parts[1] + '/' + parts[0];
|
|
1582
|
+
return parts[0] + ' ' + parts[2] + '/' + parts[1];
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* speToNonSpe: get the nonspecific OBL of a specific one
|
|
1587
|
+
*
|
|
1588
|
+
* @param {string} obl the specific obl
|
|
1589
|
+
* @returns {string} the unspecific obl
|
|
1590
|
+
*/
|
|
1591
|
+
speToNonSpe(obl) {
|
|
1592
|
+
const [uObl, dObl] = obl.split('/');
|
|
1593
|
+
const u = uObl.split(' ').pop();
|
|
1594
|
+
const d = dObl.split(' ').pop();
|
|
1595
|
+
const candidates = SquanLib.POSSIBLE_OBL
|
|
1596
|
+
.filter(c => c.includes(u) && c.includes(d))
|
|
1597
|
+
.map(c => this.oblName(c));
|
|
1598
|
+
for (const cand of candidates) {
|
|
1599
|
+
const specials = SquanLib.OBL_TRANSLATION[cand] || [];
|
|
1600
|
+
for (const spe of specials) {
|
|
1601
|
+
if (spe === obl) return cand;
|
|
1602
|
+
const [s1, s2] = spe.split('/');
|
|
1603
|
+
if (`${s2}/${s1}` === obl) return this.layerFlipName(cand);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
throw new Error(`speToNonSpe: No non-specific OBL found for: ${obl}`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* isOBLCase: checks if a CSP-style OBL state is an OBL
|
|
1611
|
+
*
|
|
1612
|
+
* @param {string} l the CSP-style state
|
|
1613
|
+
* @param {string} target the OBL name (one layer)
|
|
1614
|
+
* @returns {number | boolean} the angle offset, 1-4, or false
|
|
1615
|
+
*/
|
|
1616
|
+
isOBLCase(l, target) {
|
|
1617
|
+
const targetPattern = Object.entries(SquanLib.OBLToEnglish)
|
|
1618
|
+
.find(([, v]) => v === target
|
|
1619
|
+
)?.[0];
|
|
1620
|
+
if (!targetPattern) return false;
|
|
1621
|
+
// to corner first
|
|
1622
|
+
if (l[0] !== l[0].toUpperCase()) l = this.shift(l, -1);
|
|
1623
|
+
for (let m = 0; m < 4; m++) {
|
|
1624
|
+
if (targetPattern === this.shift(l, -3 * m)) return m;
|
|
1625
|
+
}
|
|
1626
|
+
const noTT = !['T', 'tie'].includes(target.split(' ').pop());
|
|
1627
|
+
if (noTT) {
|
|
1628
|
+
// free to change the color
|
|
1629
|
+
const fl = this.layerFlip(l);
|
|
1630
|
+
for (let m = 0; m < 4; m++) {
|
|
1631
|
+
if (targetPattern === this.shift(fl, -3 * m)) return m;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* layerToOBL: convert a CSP-style "BbWw" layer to the OBL name and angle offset
|
|
1639
|
+
*
|
|
1640
|
+
* @param {string} layer CSP-style OBL layer
|
|
1641
|
+
* @returns {{obl: string, angleOffset: number}} the obl and and angle offset
|
|
1642
|
+
*/
|
|
1643
|
+
layerToOBL(layer) {
|
|
1644
|
+
for (const obl of Object.keys(SquanLib.OBLToState))
|
|
1645
|
+
if (this.isOBLCase(layer, obl))
|
|
1646
|
+
return { obl, angleOffset: this.isOBLCase(layer, obl) };
|
|
1647
|
+
throw new Error('layerToOBL: no OBL matched layer: ' + layer);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* getAngle: get the angle from the OBL cases and angle offsets
|
|
1654
|
+
*
|
|
1655
|
+
* @param {string} u new naming U layer OBL
|
|
1656
|
+
* @param {string} d new naming D layer OBL
|
|
1657
|
+
* @param {number} au U layer angle offset
|
|
1658
|
+
* @param {number} ad D layer angle offset
|
|
1659
|
+
* @returns {string} the angle inside a <>
|
|
1660
|
+
* @example "left pair", "right arrow", "1", "2" → "DL DL"
|
|
1661
|
+
*/
|
|
1662
|
+
getAngle(u, d, au, ad) {
|
|
1663
|
+
let uAngle = SquanLib.OBL_ANGLES[u];
|
|
1664
|
+
let dAngle = SquanLib.OBL_ANGLES[d];
|
|
1665
|
+
for (let i = 0; i < au % 4; i++) uAngle = SquanLib.nextAngle[uAngle];
|
|
1666
|
+
for (let i = 0; i < ad % 4; i++) dAngle = SquanLib.nextAngle[dAngle];
|
|
1667
|
+
return `${uAngle} ${dAngle}`;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* cubeToSpe: convert an OBL cube state to the OBL of the layers and their
|
|
1672
|
+
* angle offset
|
|
1673
|
+
*
|
|
1674
|
+
* @param {string} state the CSP-style OBL cube state
|
|
1675
|
+
* @returns {[{obl: string, angleOffset: number}, {obl: string, angleOffset: number}]}
|
|
1676
|
+
* the result
|
|
1677
|
+
*/
|
|
1678
|
+
cubeToSpe(state) {
|
|
1679
|
+
return [
|
|
1680
|
+
this.layerToOBL(state.slice(0, SquanLib.LAYERL)),
|
|
1681
|
+
this.layerToOBL(state.slice(SquanLib.LAYERL))
|
|
1682
|
+
];
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* getOBLLen: get the optimal slicecount for the OBL
|
|
1687
|
+
*
|
|
1688
|
+
* @param {string} o nonspe OBL
|
|
1689
|
+
* @returns {number} optimal slicecount for it
|
|
1690
|
+
* @example "good bunny/thumb" → 4
|
|
1691
|
+
*/
|
|
1692
|
+
getOBLLen(o) {
|
|
1693
|
+
if (o in OBL_LEN) return OBL_LEN[o];
|
|
1694
|
+
return OBL_LEN[layer_flip_name(o)];
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* stateToLen: get the optimal slicecount for this CSP-style OBL cube state
|
|
1699
|
+
*
|
|
1700
|
+
* @param {string} u U layer CSP-style OBL state
|
|
1701
|
+
* @param {string} d D layer CSP-style OBL state
|
|
1702
|
+
* @returns {number} the optimal slicecount for the OBL
|
|
1703
|
+
*/
|
|
1704
|
+
stateToLen(u, d) {
|
|
1705
|
+
return this.getOBLLen(this.speToNonSpe(u + '/' + d));
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* getOBLNaming: get the new naming from the old naming
|
|
1710
|
+
*
|
|
1711
|
+
* @param {string} u the U layer old naming
|
|
1712
|
+
* @param {string} d the D layer old naming
|
|
1713
|
+
* @returns {string} the matt naming for the case, slash separated
|
|
1714
|
+
* @example "left bunny", "right thumb" → "Uc/Thc"
|
|
1715
|
+
*/
|
|
1716
|
+
getOBLNaming(u, d) {
|
|
1717
|
+
return SquanLib.NAMING[u] + '/' + SquanLib.NAMING[d];
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* stateToMatt: convert an CSP-style OBL cube state to matt tracing memo
|
|
1723
|
+
*
|
|
1724
|
+
* @param {string} s CSP-style OBL cube state
|
|
1725
|
+
* @returns {string} matt tracing memo for the state
|
|
1726
|
+
*/
|
|
1727
|
+
stateToMatt(s) {
|
|
1728
|
+
let u = s.slice(0, SquanLib.LAYERL), d = s.slice(SquanLib.LAYERL);
|
|
1729
|
+
u = (u[0] !== u[0].toLowerCase()) ? this.shift(u, 3) : this.shift(u, 2);
|
|
1730
|
+
d = (d[0] !== d[0].toLowerCase()) ? this.shift(d, 3) : this.shift(d, 2);
|
|
1731
|
+
let mem = '';
|
|
1732
|
+
let p = 1;
|
|
1733
|
+
for (let x = 0; x < SquanLib.LAYERL; x += 3) {
|
|
1734
|
+
// do one "pair" at one time
|
|
1735
|
+
if (u[x] === 'B') mem += p;
|
|
1736
|
+
if (u[x + 2] === 'b') mem += (p + 1);
|
|
1737
|
+
p += 2;
|
|
1738
|
+
}
|
|
1739
|
+
// if it's just the solved state
|
|
1740
|
+
mem = (mem === '') ? '- ' : mem + ' ';
|
|
1741
|
+
p = 1;
|
|
1742
|
+
for (let x = 0; x < SquanLib.LAYERL; x += 3) {
|
|
1743
|
+
if (d[x] === 'B') mem += p;
|
|
1744
|
+
if (d[x + 2] === 'b') mem += (p + 1);
|
|
1745
|
+
p += 2;
|
|
1746
|
+
}
|
|
1747
|
+
return (mem[mem.length - 1] === ' ') ? mem + '-' : mem;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* mattToLayer: converts one layer matt tracing memo to OBL layer state
|
|
1752
|
+
*
|
|
1753
|
+
* @param {string} m matt tracing memo, one layer
|
|
1754
|
+
* @returns {string} CSP-style OBL layer
|
|
1755
|
+
*/
|
|
1756
|
+
mattToLayer(m) {
|
|
1757
|
+
const bw = ['W', 'W', 'w', 'W', 'W', 'w', 'W', 'W', 'w', 'W', 'W', 'w'];
|
|
1758
|
+
for (const ch of m) {
|
|
1759
|
+
const num = parseInt(ch, 10);
|
|
1760
|
+
if (num % 2 !== 0) {
|
|
1761
|
+
// corner
|
|
1762
|
+
bw[Math.floor(num / 2) * 3] = 'B';
|
|
1763
|
+
bw[Math.floor(num / 2) * 3 + 1] = 'B';
|
|
1764
|
+
} else {
|
|
1765
|
+
// edge
|
|
1766
|
+
bw[Math.floor(num / 2) * 3 - 1] = 'b';
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return bw.join('');
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* mattToNonSpe: converts a full matt tracing memo to non specific OBL name
|
|
1774
|
+
*
|
|
1775
|
+
* @param {string} m a full matt tracing memo
|
|
1776
|
+
* @returns {string} a 2-layer nonspecific OBL name
|
|
1777
|
+
*/
|
|
1778
|
+
mattToNonSpe(m) {
|
|
1779
|
+
const [u, d] = m.split(' ');
|
|
1780
|
+
return this.speToNonSpe(
|
|
1781
|
+
`${this.layerToOBL(this.mattToLayer(u))}/${this.layerToOBL(this.mattToLayer(d))}`
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* sortOblp: sort one layer of matt tracing OBLP memo
|
|
1787
|
+
*
|
|
1788
|
+
* @param {string} seq one layer of matt tracing OBLP memo
|
|
1789
|
+
* @returns {string} the sorted memo
|
|
1790
|
+
*/
|
|
1791
|
+
sortOblp(seq) {
|
|
1792
|
+
return [...seq].sort().join('');
|
|
1793
|
+
}
|
|
1048
1794
|
}
|