From 523fe1e4aaf36829623b8445cd52622c1021b08f Mon Sep 17 00:00:00 2001 From: Simon Kelly Date: Tue, 21 May 2024 00:40:41 +0100 Subject: [PATCH] Fix desync and timing --- .eslintrc.cjs | 6 ------ src/components/cubing/twisty.tsx | 34 +++++++++++++++++++++++++----- src/components/layout/navbar.tsx | 3 ++- src/components/timer/timerStore.ts | 19 ++++++++++++++--- src/components/ui/.eslintrc | 5 +++++ src/lib/smartCube.ts | 8 +++++++ src/lib/solutionParser.ts | 29 +++++++++++++++++-------- tests/parse.test.ts | 12 +++++------ 8 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 src/components/ui/.eslintrc diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4c99537..88898a0 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -9,10 +9,4 @@ module.exports = { ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, }; diff --git a/src/components/cubing/twisty.tsx b/src/components/cubing/twisty.tsx index 693ce69..39ff14b 100644 --- a/src/components/cubing/twisty.tsx +++ b/src/components/cubing/twisty.tsx @@ -4,11 +4,14 @@ import { useEffect, useRef, useState } from 'react'; import { CubeStore } from '@/lib/smartCube'; import { cn } from '@/lib/utils'; import cubeImage from '/cube-colors.png'; +import { experimentalSolve3x3x3IgnoringCenters } from 'cubing/search'; +import { GanCubeMove } from 'gan-web-bluetooth'; export default function Twisty({ className }: { className: string }) { const containerRef = useRef(null); const [player, setPlayer] = useState(null); - const moves = useStore(CubeStore, state => state.lastMoves); + + const cube = useStore(CubeStore, state => state.cube); const startingState = useStore(CubeStore, state => state.startingState); useEffect(() => { @@ -35,13 +38,34 @@ export default function Twisty({ className }: { className: string }) { }, [containerRef]); useEffect(() => { - if (!player) return; + if (!player || !cube) return; player.alg = startingState ?? ''; - moves?.forEach(move => { - player.experimentalAddMove(move.move); + + const moves: GanCubeMove[] = []; + let sub = cube.events$.subscribe(ev => { + if (ev.type !== 'MOVE') return; + moves.push(ev); }); - }, [player, startingState, moves]); + + experimentalSolve3x3x3IgnoringCenters(CubeStore.state.kpattern!).then(solution => { + player.alg = solution.invert(); + + sub.unsubscribe(); + moves.forEach(move => { + player.experimentalAddMove(move.move); + }); + + sub = cube.events$.subscribe(ev => { + if (ev.type !== 'MOVE') return; + player.experimentalAddMove(ev.move); + }); + }); + + return () => { + sub.unsubscribe(); + } + }, [player, startingState, cube]); const classes = cn('flex', className); return
; diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx index cd41341..dbc66cd 100644 --- a/src/components/layout/navbar.tsx +++ b/src/components/layout/navbar.tsx @@ -6,8 +6,9 @@ import { CubeStore, connect, reset } from '@/lib/smartCube'; function CubeStatus() { const cube = useStore(CubeStore, state => state.cube); - const onClick = async () => { + const onClick = async (ev: React.MouseEvent) => { connect(); + ev.currentTarget.blur(); }; return ( diff --git a/src/components/timer/timerStore.ts b/src/components/timer/timerStore.ts index bcdaca1..1485c0f 100644 --- a/src/components/timer/timerStore.ts +++ b/src/components/timer/timerStore.ts @@ -2,7 +2,11 @@ import { Store, useStore } from '@tanstack/react-store'; import { Alg } from 'cubing/alg'; import { randomScrambleForEvent } from 'cubing/scramble'; import { experimentalSolve3x3x3IgnoringCenters } from 'cubing/search'; -import { GanCubeEvent, GanCubeMove } from 'gan-web-bluetooth'; +import { + GanCubeEvent, + GanCubeMove, + cubeTimestampLinearFit, +} from 'gan-web-bluetooth'; import { useEffect, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useStopwatch } from 'react-use-precision-timer'; @@ -100,6 +104,7 @@ async function processScrambleMove(ev: GanCubeMove) { const newScramble = async () => { const scramble = await randomScrambleForEvent('333'); + console.log('New Scramble: ', scramble.toString()); TimerStore.setState(state => ({ ...state, originalScramble: scramble.toString(), @@ -120,7 +125,8 @@ export const useCubeTimer = () => { processScrambleMove(event); }); - setScrambleFromCubeState(TimerStore.state.originalScramble); + if (TimerStore.state.originalScramble) + setScrambleFromCubeState(TimerStore.state.originalScramble); return () => { subscription?.unsubscribe(); @@ -156,13 +162,20 @@ export const useCubeTimer = () => { move => !moveDetails.current.start.includes(move) ); + const times = cubeTimestampLinearFit(diff); const solution = diff.map(move => move.move).join(' '); const algs = await extractAlgs(solution); console.log('Scramble:', TimerStore.state.originalScramble); - console.log(algs.join('\n')); console.log('Solution:', solution); + let last = times[0].cubeTimestamp; + algs.forEach(([alg, idx]) => { + const ms = times[idx].cubeTimestamp - last; + const time = (ms / 1000).toFixed(2); + console.log(alg, '// ' + time); + last = times[idx].cubeTimestamp; + }); newScramble(); }; diff --git a/src/components/ui/.eslintrc b/src/components/ui/.eslintrc new file mode 100644 index 0000000..d29e07e --- /dev/null +++ b/src/components/ui/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "react-refresh/only-export-components": "off" + } +} \ No newline at end of file diff --git a/src/lib/smartCube.ts b/src/lib/smartCube.ts index 606b508..42c737f 100644 --- a/src/lib/smartCube.ts +++ b/src/lib/smartCube.ts @@ -38,9 +38,17 @@ type CubeStoreType = { export const CubeStore = new Store({} as CubeStoreType); +let last = 0; + async function handleMoveEvent(event: GanCubeEvent) { if (event.type !== 'MOVE') return; + const diff = event.serial - last; + if (diff != 255 && diff != 1) { + console.log('Move:', event.serial, 'Last:', last); + } + last = event.serial; + CubeStore.setState(state => { let lastMoves = state.lastMoves ?? []; lastMoves = [...lastMoves, event]; diff --git a/src/lib/solutionParser.ts b/src/lib/solutionParser.ts index 56a5032..5a47a3d 100644 --- a/src/lib/solutionParser.ts +++ b/src/lib/solutionParser.ts @@ -254,16 +254,20 @@ export function simplify(alg: string) { .toString(); } -export async function extractAlgs(solution: string): Promise { +export async function extractAlgs( + solution: string +): Promise<[string, number][]> { const moveSet = [...solution.split(' ')]; - const comms: string[] = []; + const comms: [string, number][] = []; let moves = ''; let count = 0; const puzzle = await cube3x3x3.kpuzzle(); + let moveIdx = -1; while (moveSet.length > 0) { + moveIdx++; const move = moveSet.shift()!; moves += ' ' + move; if (count++ < 4 || moveSet.length === 0) continue; @@ -277,8 +281,8 @@ export async function extractAlgs(solution: string): Promise { continue; if (uncancelled.length > 0) - comms.push((moves + ' ' + uncancelled.alg + '').trim()); - else comms.push((moves + ' ' + uncancelled.alg).trim()); + comms.push([(moves + ' ' + uncancelled.alg + '').trim(), moveIdx]); + else comms.push([(moves + ' ' + uncancelled.alg).trim(), moveIdx]); count = uncancelled.length; // TODO: There might be a flaw in the logic.... @@ -286,13 +290,13 @@ export async function extractAlgs(solution: string): Promise { moves = Alg.fromString(uncancelled.alg).invert().toString(); } - if (moves.length > 0) comms.push(moves); + if (moves.length > 0) comms.push([moves, moveIdx]); - return comms.map(comm => { + return comms.map(val => { + const comm = val[0]; const alg = simplify( removeRotations(convertToSliceMoves(simplify(comm).split(' '))).join(' ') ); - console.log(alg); let foundComm = commutator.search({ algorithm: alg, outerBracket: true, @@ -305,7 +309,14 @@ export async function extractAlgs(solution: string): Promise { })[0]; } - if (foundComm.endsWith('.')) return simplify(comm.trim()) + ' // not found'; - return foundComm.replaceAll(',', ', ').replaceAll(':', ': '); + if (foundComm.endsWith('.')) + return [simplify(comm.trim()) + ' // not found', val[1]] as [ + string, + number, + ]; + return [foundComm.replaceAll(',', ', ').replaceAll(':', ': '), val[1]] as [ + string, + number, + ]; }); } diff --git a/tests/parse.test.ts b/tests/parse.test.ts index 0f449d6..77d07d2 100644 --- a/tests/parse.test.ts +++ b/tests/parse.test.ts @@ -8,19 +8,19 @@ import { test('Extract valid comm (EP)', async () => { const extracted = await extractAlgs("R U' R' D' R U2 R' D R U' R'"); expect(extracted.length).toBe(1); - expect(extracted[0]).toBe("[R U': [R' D' R, U2]]"); + expect(extracted[0][0]).toBe("[R U': [R' D' R, U2]]"); }); test('Extract valid comm (BG)', async () => { const extracted = await extractAlgs("D' R' D R U R' D' R U' D"); expect(extracted.length).toBe(1); - expect(extracted[0]).toBe("[D': [R' D R, U]]"); + expect(extracted[0][0]).toBe("[D': [R' D R, U]]"); }); test('Extract invalid comm', async () => { const extracted = await extractAlgs("R U' R' "); expect(extracted.length).toBe(1); - expect(extracted[0]).toBe("R U' R' // not found"); + expect(extracted[0][0]).toBe("R U' R' // not found"); }); test('Converts slice moves and rotations (DO)', () => { @@ -33,13 +33,13 @@ test('Converts slice moves and rotations (DO)', () => { test('Extract slice alg (FK)', async () => { const extracted = await extractAlgs("F' B L D L' R F' F' L R' D L' F B'"); expect(extracted.length).toBe(1); - expect(extracted[0]).toBe("[S U L: [E', L2]]"); + expect(extracted[0][0]).toBe("[S U L: [E', L2]]"); }); test('Extract slice alg (GJ) with double moves', async () => { const extracted = await extractAlgs("R' L F R' F' L R' D R D' L L R' R'"); expect(extracted.length).toBe(1); - expect(extracted[0]).toBe("[M': [U R' U', M']]"); + expect(extracted[0][0]).toBe("[M': [U R' U', M']]"); }); test('Extract slice alg (FK) with extra moves', async () => { @@ -47,7 +47,7 @@ test('Extract slice alg (FK) with extra moves', async () => { "R U B D D' B' U' R' F' B L D L' R F' F' L R' D L' F B'" ); expect(extracted.length).toBe(1); - expect(extracted[0]).toBe("[S U L: [E', L2]]"); + expect(extracted[0][0]).toBe("[S U L: [E', L2]]"); }); test('Converts slice moves M', () => {