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
29 changes: 26 additions & 3 deletions web/src/cases/algs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import { cube3x3x3 } from 'cubing/puzzles'
import { describe, expect, it } from 'vitest'
import { patternToFacelets } from '../cube/facelets'
import { stageLevel } from '../solve/cfop'
import { ALT_ALGS } from './algs'
import { algsFor, ALT_ALGS, defaultAlg, usesF } from './algs'
import { ALL_CASES } from './data'
import { applyNormalized, caseSignature } from './geometry'
import { applyNormalized, caseSignature, ROTATIONS } from './geometry'

const kpuzzle = await cube3x3x3.kpuzzle()

const caseState = (alg: string) => applyNormalized(kpuzzle, new Alg(alg).invert().toString())
/** 手順の最後に、全体の向きを元に戻す持ち替えを足す(y を含む手順は、回し終えると向きが変わっているため)。 */
function closeRotation(alg: string): string {
const solved = kpuzzle.defaultPattern()
const r = ROTATIONS.find((r) => solved.applyAlg(`${alg} ${r}`).patternData.CENTERS.pieces.every((v, i) => v === i))
return r ? `${alg} ${r}` : alg
}

const caseState = (alg: string) => applyNormalized(kpuzzle, new Alg(closeRotation(alg)).invert().toString())
const level = (alg: string) => stageLevel(patternToFacelets(caseState(alg)), 'yellow')

const rows = Object.entries(ALT_ALGS).flatMap(([id, algs]) => algs.map((alg) => [id, alg] as const))
Expand All @@ -26,3 +33,19 @@ describe('alternative algs', () => {
expect(alg).not.toBe(c.alg)
})
})

describe('F2L without F', () => {
const f2l = ALL_CASES.filter((c) => c.set === 'F2L')

it('every F2L case has an alg without F, and it is the default', () => {
for (const c of f2l) {
expect(algsFor(c).some((a) => !usesF(a)), c.id).toBe(true)
expect(usesF(defaultAlg(c)), c.id).toBe(false)
}
})

it('keeps the standard alg as the default outside F2L', () => {
const oll = ALL_CASES.find((c) => c.id === 'oll-45')!
expect(defaultAlg(oll)).toBe(oll.alg)
})
})
55 changes: 52 additions & 3 deletions web/src/cases/algs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,43 @@ export const ALT_ALGS: Record<string, string[]> = {
'pll-Na': ["L U' R U2 L' U R' L U' R U2 L' U R'"],
'pll-Nb': ["R' U L' U2 R U' L R' U L' U2 R U' L"],
'pll-V': ["R' U R U' R' f' U' R U2 R' U' R U' R' f R"],
// F2L:F を使わない手順(標準手順が F を使うケースだけ)。R・U だけで解けるケースはその手順を先に、
// エッジの向きが悪く R・U だけでは解けないケースは、y で持ち替えて左手(L' U L など)で入れる手順。
'f2l-1': ["y L' U' L"],
'f2l-3': ["U' y L' U L"],
'f2l-4': ["U R U' R'"],
'f2l-5': ["R U R' U2 R U' R' U R U' R'", "y L' U L U2 y' R U R'"],
'f2l-6': ["y L' U2 L U L' U' L"],
'f2l-7': ["R U' R' U2 y L' U' L"],
'f2l-9': ["R U' R' U2 R U R'"],
'f2l-10': ["U2 R U R' y L' U' L"],
'f2l-11': ["U y L' U L U' L' U' L"],
'f2l-12': ["U' R U R' U R U R'"],
'f2l-13': ["U y L' U' L U L' U2 L"],
'f2l-14': ["U y L' U2 L U L' U2 L"],
'f2l-15': ["R U' R' U R U' R' U' R U2 R'", "U y L' U2 L U' y' R U R'"],
'f2l-16': ["U y L' U' L U' L' U' L"],
'f2l-17': ["U R U2 R' U R U' R'"],
'f2l-18': ["U' y L' U2 L U' L' U L"],
'f2l-21': ["U' R U2 R' U y L' U' L"],
'f2l-23': ["R U R' U R U2 R' y L' U2 L"],
'f2l-24': ["R U R' U R U2 R' U2 R U R'"],
'f2l-25': ["R U' R' y L' U2 L"],
'f2l-26': ["U' R U' R' U' R U2 R'"],
'f2l-28': ["U y L' U' L U' y' R U R'"],
'f2l-29': ["U' R U R' U y L' U' L"],
'f2l-30': ["R U R' U' R U R' U' R U R'"],
'f2l-31': ["R U2 R' y L' U2 L"],
'f2l-32': ["R U' R' U R U R' U2 R U R'", "y L' U2 L U2 y' R U R'"],
'f2l-33': ["U' R U R' U' R U R'"],
'f2l-34': ["U' R U' R' U R U' R'"],
'f2l-35': ["U2 R U' R' y L' U' L"],
'f2l-36': ["U R U R' U' y L' U' L"],
'f2l-37': ["y L' U L U2 L' U' L y' R U R'"],
'f2l-38': ["R U' R' U2 R U R' y L' U' L"],
'f2l-39': ["R U R' U' R U2 R' U' R U R'"],
'f2l-40': ["R U' R' U y L' U2 L U L' U2 L"],
'f2l-41': ["R U R' U2 R U' R' U R U R'"],
}

/** ケースで選べる手順(先頭が標準手順)。 */
Expand All @@ -55,15 +92,27 @@ export function algsFor(c: { id: string; alg: string }): string[] {

const algKey = (id: string) => `cubalyst.drillAlg.${id}`

/** 手順練習で前回選んだ手順(選べる手順に無ければ標準手順)。 */
/** 手順に F(F・F'・F2)が入っているか。 */
export const usesF = (alg: string) => alg.split(/\s+/).some((m) => m[0] === 'F')

/**
* 初めて選ぶときの手順。F2L は、標準手順が F を使うなら、F を使わない別の手順にする。
* F2L の F はエッジの向きを直すためなので、R・U(と持ち替えて左手)で回せる手順を先に練習できるようにする。
*/
export function defaultAlg(c: { id: string; alg: string }): string {
if (!c.id.startsWith('f2l-') || !usesF(c.alg)) return c.alg
return algsFor(c).find((a) => !usesF(a)) ?? c.alg
}

/** 手順練習で前回選んだ手順(選べる手順に無ければ初めて選ぶときの手順)。 */
export function preferredAlg(c: { id: string; alg: string }): string {
try {
const v = localStorage.getItem(algKey(c.id))
if (v && algsFor(c).includes(v)) return v
} catch {
// 読めなければ標準手順
// 読めなければ初めて選ぶときの手順
}
return c.alg
return defaultAlg(c)
}

/** 手順練習で選んだ手順を覚えておく。 */
Expand Down
7 changes: 7 additions & 0 deletions web/src/cases/drill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ describe('AlgDrill', () => {
expect(events[0].attempt.ms).toBe((moves.length - 1) * 100)
})

it('accepts an algorithm with a y rotation in it', () => {
// y L' U' L は、キューブから見ると(持ち替えは記録されないので)F' U' F
const { events, turn } = setup("y L' U' L")
toCubeMoves("F' U' F").forEach((m) => turn(m))
expect(events).toHaveLength(1)
})

it('accepts AUF before and after the algorithm', () => {
const { events, turn } = setup(TPERM)
toCubeMoves(`U ${TPERM} U2`).forEach((m) => turn(m))
Expand Down
Loading