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                          38x 38x 38x     24x 24x     38x   49x 43x 43x   6x 6x           24x 24x 24x 24x 24x 2x 2x   22x 22x 22x 41x 22x 22x 22x 22x     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;
    },
  };
}