Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions web/src/cases/algs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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')

Expand Down
2 changes: 1 addition & 1 deletion web/src/cases/algs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const ALT_ALGS: Record<string, string[]> = {
'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"],
Expand Down
13 changes: 13 additions & 0 deletions web/src/cases/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>()

/** ケースの図を、その手順で揃う向きにしたもの(別手順はヘッドライトなどの向きが標準手順と違うことがある)。 */
export function caseForAlg<T extends { alg: string; pattern: string }>(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 {
Expand Down
11 changes: 7 additions & 4 deletions web/src/pages/AlgDrillPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -56,6 +57,7 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null
const [selectedId, setSelectedId] = useState<string | null>(() => (setOf(initialCaseId) ? initialCaseId! : null))
const [running, setRunning] = useState(false)
const [chosenAlg, setChosenAlg] = useState<Record<string, string>>({})
const store = useCubeStore()
const history = useSolveList({ mode: 'case', limit: 0 })
const stats = useMemo(() => caseStats(history.solves), [history.solves])

Expand All @@ -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)
Expand Down Expand Up @@ -106,7 +109,7 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null
{selected && (
<section className="panel drill-selected" aria-live="polite">
<div className="row">
<CaseDiagram c={selected} size={96} arrows />
<CaseDiagram c={caseForAlg(store.kpuzzle, selected, alg)} size={96} arrows />
<div className="drill-selected-info">
<div className="row">
<strong>{selected.name}</strong>
Expand Down Expand Up @@ -165,7 +168,7 @@ export function AlgDrillPage({ initialCaseId }: { initialCaseId?: string | null
onClick={() => setSelectedId(c.id)}
title={c.alg}
>
<CaseDiagram c={c} />
<CaseDiagram c={caseForAlg(store.kpuzzle, c, algOf(c))} />
<div className="case-name">
{c.learned && <span className="learned-mark" aria-label="覚えた">✓</span>}
{c.name}
Expand Down Expand Up @@ -296,7 +299,7 @@ function DrillView({ c, alg, onBack, onSaved }: { c: CaseInfo; alg: string; onBa
</div>

<section className="panel drill-head">
<CaseDiagram c={c} size={88} arrows />
<CaseDiagram c={caseForAlg(store.kpuzzle, c, alg)} size={88} arrows />
<div>
<strong>{c.name}</strong>
<div className="alg drill-alg">{alg}</div>
Expand Down
4 changes: 3 additions & 1 deletion web/src/pages/AssistPractice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -253,6 +254,7 @@ function CaseHint({
show: boolean
missing?: string
}) {
const store = useCubeStore()
return (
<div className="assist-case row">
{place && <span className="assist-slot">{place}</span>}
Expand All @@ -262,7 +264,7 @@ function CaseHint({
<span className="muted">考え中…</span>
) : (
<>
{info && <CaseDiagram c={info} size={56} arrows />}
{info && <CaseDiagram c={caseForAlg(store.kpuzzle, info, preferredAlg(info))} size={56} arrows />}
<div>
<div>
<strong>{c.name}</strong>
Expand Down
Loading