diff --git a/web/src/cases/algs.test.ts b/web/src/cases/algs.test.ts index b695d98..7e17aae 100644 --- a/web/src/cases/algs.test.ts +++ b/web/src/cases/algs.test.ts @@ -6,6 +6,7 @@ import { stageLevel } from '../solve/cfop' import { algsFor, ALT_ALGS, defaultAlg, usesF } from './algs' import { ALL_CASES } from './data' import { applyNormalized, caseSignature, ROTATIONS } from './geometry' +import { caseForAlg } from './seed' const kpuzzle = await cube3x3x3.kpuzzle() @@ -34,6 +35,29 @@ describe('alternative algs', () => { }) }) +describe('diagram follows the chosen alg', () => { + const c = (id: string) => ALL_CASES.find((x) => x.id === id)! + // 面の上段の両端のシール(R:9・11、F:18・20)が同じ色ならヘッドライト + const headlights = (f: string, a: number, b: number) => f[a] === f[b] + + it("draws Aa's headlights on the right for x' R2 D2 R' U' R D2 R' U R' x", () => { + const alg = "x' R2 D2 R' U' R D2 R' U R' x" + expect(algsFor(c('pll-Aa'))).toContain(alg) + expect(headlights(caseForAlg(kpuzzle, { alg: c('pll-Aa').alg, pattern: '' }, alg).pattern, 9, 11)).toBe(true) + }) + + it("draws Ab's headlights in front for R B' R F2 R' B R F2 R2", () => { + const alg = "R B' R F2 R' B R F2 R2" + expect(algsFor(c('pll-Ab'))).toContain(alg) + expect(headlights(caseForAlg(kpuzzle, { alg: c('pll-Ab').alg, pattern: '' }, alg).pattern, 18, 20)).toBe(true) + }) + + it('keeps the stored pattern for the standard alg', () => { + const x = { alg: "R U R'", pattern: 'stored' } + expect(caseForAlg(kpuzzle, x, x.alg)).toBe(x) + }) +}) + describe('F2L without F', () => { const f2l = ALL_CASES.filter((c) => c.set === 'F2L') diff --git a/web/src/cases/algs.ts b/web/src/cases/algs.ts index bde1ccb..299c5df 100644 --- a/web/src/cases/algs.ts +++ b/web/src/cases/algs.ts @@ -29,7 +29,7 @@ export const ALT_ALGS: Record = { 'oll-51': ["f R U R' U' R U R' U' f'"], 'oll-52': ["R' F' U' F U' R U R' U R"], 'oll-57': ["R U R' U' r R' U R U' r'"], - 'pll-Aa': ["x L2 D2 L' U' L D2 L' U L' x'", "R' F R' B2 R F' R' B2 R2"], + 'pll-Aa': ["x L2 D2 L' U' L D2 L' U L' x'", "R' F R' B2 R F' R' B2 R2", "x' R2 D2 R' U' R D2 R' U R' x"], 'pll-Ab': ["x' L2 D2 L U L' D2 L U' L x", "R B' R F2 R' B R F2 R2"], 'pll-H': ["M2 U' M2 U2 M2 U' M2", 'R2 U2 R U2 R2 U2 R2 U2 R U2 R2'], 'pll-Ua': ["R U' R U R U R U' R' U' R2", "R2 U' R' U' R U R U R U' R"], diff --git a/web/src/cases/seed.ts b/web/src/cases/seed.ts index ad5882d..78b5eff 100644 --- a/web/src/cases/seed.ts +++ b/web/src/cases/seed.ts @@ -11,6 +11,19 @@ export function casePattern(kpuzzle: KPuzzle, alg: string): string { return patternToFacelets(applyNormalized(kpuzzle, new Alg(alg).invert().toString())) } +const patterns = new Map() + +/** ケースの図を、その手順で揃う向きにしたもの(別手順はヘッドライトなどの向きが標準手順と違うことがある)。 */ +export function caseForAlg(kpuzzle: KPuzzle, c: T, alg: string): T { + if (!alg || alg === c.alg) return c + let pattern = patterns.get(alg) + if (pattern === undefined) { + pattern = casePattern(kpuzzle, alg) + patterns.set(alg, pattern) + } + return { ...c, pattern } +} + const q = (s: string) => `'${s.replace(/'/g, "''")}'` export function casesMigrationSQL(kpuzzle: KPuzzle): string { diff --git a/web/src/pages/AlgDrillPage.tsx b/web/src/pages/AlgDrillPage.tsx index 29a575b..df876c7 100644 --- a/web/src/pages/AlgDrillPage.tsx +++ b/web/src/pages/AlgDrillPage.tsx @@ -6,6 +6,7 @@ import { useAlgDrill, useCaseSetup, useCaseSetupState, useDrillState } from '../ import type { CaseSetup, CaseSetupState } from '../cases/setup' import { scrambleBetween } from '../cases/exercise' import { invertRotation, normalizeOrientation } from '../cases/geometry' +import { caseForAlg } from '../cases/seed' import { caseStats } from '../cases/stats' import { CaseDiagram } from '../components/CaseDiagram' import { FitLayout } from '../components/FitLayout' @@ -56,6 +57,7 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null const [selectedId, setSelectedId] = useState(() => (setOf(initialCaseId) ? initialCaseId! : null)) const [running, setRunning] = useState(false) const [chosenAlg, setChosenAlg] = useState>({}) + const store = useCubeStore() const history = useSolveList({ mode: 'case', limit: 0 }) const stats = useMemo(() => caseStats(history.solves), [history.solves]) @@ -74,7 +76,8 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null setError(`「覚えた」を保存できませんでした: ${String(err)}`) }) } - const alg = selected ? (chosenAlg[selected.id] ?? preferredAlg(selected)) : '' + const algOf = (c: CaseInfo) => chosenAlg[c.id] ?? preferredAlg(c) + const alg = selected ? algOf(selected) : '' const chooseAlg = (id: string, a: string) => { setChosenAlg((prev) => ({ ...prev, [id]: a })) savePreferredAlg(id, a) @@ -106,7 +109,7 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null {selected && (
- +
{selected.name} @@ -165,7 +168,7 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null onClick={() => setSelectedId(c.id)} title={c.alg} > - +
{c.learned && ✓} {c.name} @@ -296,7 +299,7 @@ function DrillView({ c, alg, onBack, onSaved }: { c: CaseInfo; alg: string; onBa
- +
{c.name}
{alg}
diff --git a/web/src/pages/AssistPractice.tsx b/web/src/pages/AssistPractice.tsx index a4dcef1..978a008 100644 --- a/web/src/pages/AssistPractice.tsx +++ b/web/src/pages/AssistPractice.tsx @@ -5,6 +5,7 @@ import { listCases, type CaseInfo, type NewSolve, type Solve } from '../api' import { stickyAssist, viewFrom, type Assist, type StickyAssist } from '../assist/detect' import { preferredAlg } from '../cases/algs' import type { CaseDef } from '../cases/data' +import { caseForAlg } from '../cases/seed' import { CaseDiagram } from '../components/CaseDiagram' import { PracticeRunner } from '../components/PracticeRunner' import { useCubeStore } from '../cube/hooks' @@ -253,6 +254,7 @@ function CaseHint({ show: boolean missing?: string }) { + const store = useCubeStore() return (
{place && {place}} @@ -262,7 +264,7 @@ function CaseHint({ 考え中… ) : ( <> - {info && } + {info && }
{c.name}