Skip to content

Commit

Permalink
Fix desync and timing
Browse files Browse the repository at this point in the history
  • Loading branch information
simonkellly committed May 20, 2024
1 parent 8185f56 commit 523fe1e
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 30 deletions.
6 changes: 0 additions & 6 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
],
},
};
34 changes: 29 additions & 5 deletions src/components/cubing/twisty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>(null);
const [player, setPlayer] = useState<TwistyPlayer | null>(null);
const moves = useStore(CubeStore, state => state.lastMoves);

const cube = useStore(CubeStore, state => state.cube);
const startingState = useStore(CubeStore, state => state.startingState);

useEffect(() => {
Expand All @@ -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 <div className={classes} ref={containerRef} />;
Expand Down
3 changes: 2 additions & 1 deletion src/components/layout/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLButtonElement>) => {
connect();
ev.currentTarget.blur();
};

return (
Expand Down
19 changes: 16 additions & 3 deletions src/components/timer/timerStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(),
Expand All @@ -120,7 +125,8 @@ export const useCubeTimer = () => {
processScrambleMove(event);
});

setScrambleFromCubeState(TimerStore.state.originalScramble);
if (TimerStore.state.originalScramble)
setScrambleFromCubeState(TimerStore.state.originalScramble);

return () => {
subscription?.unsubscribe();
Expand Down Expand Up @@ -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();
};
Expand Down
5 changes: 5 additions & 0 deletions src/components/ui/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"react-refresh/only-export-components": "off"
}
}
8 changes: 8 additions & 0 deletions src/lib/smartCube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
29 changes: 20 additions & 9 deletions src/lib/solutionParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,20 @@ export function simplify(alg: string) {
.toString();
}

export async function extractAlgs(solution: string): Promise<string[]> {
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;
Expand All @@ -277,22 +281,22 @@ export async function extractAlgs(solution: string): Promise<string[]> {
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....
// Like a cancel of R2 + R = R' vs R' + R = nothing but this is not implemented
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,
Expand All @@ -305,7 +309,14 @@ export async function extractAlgs(solution: string): Promise<string[]> {
})[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,
];
});
}
12 changes: 6 additions & 6 deletions tests/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {
Expand All @@ -33,21 +33,21 @@ 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 () => {
const extracted = await extractAlgs(
"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', () => {
Expand Down

0 comments on commit 523fe1e

Please sign in to comment.