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 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 | 74x 47x 37x 98x 37x 37x 37x 37x 47x 47x 47x 47x 47x 47x 42x 2x 2x 2x 178x 47x 47x 1x 1x 1x 1x 12x 12x 12x 12x 23x 23x 37x 4x | import * as CubeFacelets from '@wstein/regrip-core/domain/CubeFacelets';
import type { SmartCubeSessionEvent } from '@wstein/regrip-core/session/smartCubeSession';
export type SolveAnalysisSnapshot = {
status: 'empty' | 'running' | 'complete';
durationMs: number;
tps: number | null;
moveCount: number;
qtm: number;
regrips: number;
triggers: number;
longestPauseMs: number;
};
const emptySnapshot = (): SolveAnalysisSnapshot => ({
status: 'empty',
durationMs: 0,
tps: null,
moveCount: 0,
qtm: 0,
regrips: 0,
triggers: 0,
longestPauseMs: 0,
});
function quarterTurns(move: string): number {
return /2'?$/.test(move.trim()) ? 2 : 1;
}
/** Summarize the current connection/replay run from its first physical turn. */
export function createSolveAnalysis(onChange?: (snapshot: SolveAnalysisSnapshot) => void) {
let value = emptySnapshot();
let startedAt: number | undefined;
let lastMoveAt: number | undefined;
const publish = (): void => onChange?.({ ...value });
function reset(): void {
value = emptySnapshot();
startedAt = undefined;
lastMoveAt = undefined;
publish();
}
function onMove(event: Extract<SmartCubeSessionEvent, { type: 'MOVE' }>): void {
if (value.status === 'complete') reset();
startedAt ??= event.timestamp;
const pause = lastMoveAt === undefined ? 0 : Math.max(0, event.timestamp - lastMoveAt);
lastMoveAt = event.timestamp;
value = {
...value,
status: 'running',
durationMs: Math.max(0, event.timestamp - startedAt),
moveCount: value.moveCount + 1,
qtm: value.qtm + quarterTurns(event.move),
longestPauseMs: Math.max(value.longestPauseMs, pause),
};
publish();
}
function complete(): void {
if (value.status !== 'running' || startedAt === undefined || lastMoveAt === undefined) return;
const durationMs = Math.max(0, lastMoveAt - startedAt);
value = {
...value,
status: 'complete',
durationMs,
tps: durationMs > 0 ? value.moveCount / (durationMs / 1000) : null,
};
publish();
}
function onEvent(event: SmartCubeSessionEvent): void {
switch (event.type) {
case 'MOVE':
onMove(event);
break;
case 'REGRIP':
if (value.status === 'running') {
value = { ...value, regrips: value.regrips + 1 };
publish();
}
break;
case 'CUSTOM_TRIGGER':
case 'SHAKE':
if (value.status === 'running') {
value = { ...value, triggers: value.triggers + 1 };
publish();
}
break;
case 'FACELETS':
if (CubeFacelets.isSolvedFacelets(event.facelets)) complete();
break;
}
}
return {
onEvent,
complete,
reset,
snapshot: (): SolveAnalysisSnapshot => ({ ...value }),
};
}
export type SolveAnalysis = ReturnType<typeof createSolveAnalysis>;
|