All files / adapters/cubing patternReconciler.ts

100% Statements 30/30
100% Branches 0/0
100% Functions 0/0
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                          37x 37x 37x     23x 23x     37x   46x 40x 40x   6x 6x           23x 23x 23x 23x 23x 2x 2x   21x 21x 21x 38x 21x 21x 21x 21x     6x 6x 6x 6x        
import type { KPattern } from 'cubing/kpuzzle';
 
import { faceletsToPattern, kpuzzleReady } from './utils';
 
/**
 * Tracks the body-frame permutation that TwistyPlayer should be showing.
 *
 * This is deliberately an adapter: cubing.js supplies the tested 3×3
 * permutation tables. The domain `PlayerSync` reducer decides only when an
 * authoritative snapshot should be applied or released.
 */
export function createPatternReconciler() {
  let expected: KPattern | undefined;
  let movesBeforeFirstSnapshot: string[] = [];
  let snapshotGeneration = 0;
  const pendingSnapshots: { expectedAtCapture: KPattern | undefined; movesAfter: string[] }[] = [];
 
  function removeSnapshot(snapshot: (typeof pendingSnapshots)[number]): void {
    const index = pendingSnapshots.indexOf(snapshot);
    if (index >= 0) pendingSnapshots.splice(index, 1);
  }
 
  return {
    applyMove(move: string): void {
      if (!expected) {
        movesBeforeFirstSnapshot.push(move);
        return;
      }
      expected = expected.applyMove(move);
      for (const snapshot of pendingSnapshots) snapshot.movesAfter.push(move);
    },
    async observeSnapshot(facelets: string): Promise<boolean> {
      // Capture the expected permutation before awaiting kpuzzle loading. BLE
      // moves may arrive during that await; they belong after this snapshot,
      // not in the comparison against it.
      const snapshot = { expectedAtCapture: expected, movesAfter: [] as string[] };
      const generation = ++snapshotGeneration;
      pendingSnapshots.push(snapshot);
      await kpuzzleReady;
      if (generation !== snapshotGeneration) {
        removeSnapshot(snapshot);
        return true;
      }
      const actual = faceletsToPattern(facelets);
      const matchesExpected = snapshot.expectedAtCapture?.isIdentical(actual) ?? false;
      expected = actual;
      for (const move of movesBeforeFirstSnapshot) expected = expected.applyMove(move);
      movesBeforeFirstSnapshot = [];
      for (const move of snapshot.movesAfter) expected = expected.applyMove(move);
      removeSnapshot(snapshot);
      return !matchesExpected;
    },
    reset(): void {
      expected = undefined;
      movesBeforeFirstSnapshot = [];
      snapshotGeneration += 1;
      pendingSnapshots.length = 0;
    },
  };
}