From 6b10e1733fc1b5206f11367c32610e1a936aaabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=83=BC=E3=82=8B?= Date: Thu, 5 Nov 2026 00:29:54 +0900 Subject: [PATCH] =?UTF-8?q?=E6=89=8B=E9=A0=86=E7=B7=B4=E7=BF=92=E3=81=AE?= =?UTF-8?q?=20F2L=20=E3=81=AB=20F=20=E3=82=92=E4=BD=BF=E3=82=8F=E3=81=AA?= =?UTF-8?q?=E3=81=84=E6=89=8B=E9=A0=86=E3=82=92=E8=B6=B3=E3=81=97=E3=80=81?= =?UTF-8?q?=E5=88=9D=E3=82=81=E3=81=A6=E9=81=B8=E3=81=B6=E3=81=A8=E3=81=8D?= =?UTF-8?q?=E3=81=AF=E3=81=9D=E3=82=8C=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 標準手順に F が入る 35 ケースのうち 34 ケースに、R・U だけの手順か、y で持ち替えて左手で入れる手順を足す。 F2L は標準手順が F を使うなら、F を使わない手順を最初に選ぶ(前に選んだ手順があればそれを使う)。 y を含む手順は回し終えると向きが変わるので、テストでは向きを戻してから同じケースかを比べる。 Co-Authored-By: Claude Opus 5.5 --- web/src/cases/algs.test.ts | 29 +++++++++++++++++-- web/src/cases/algs.ts | 55 +++++++++++++++++++++++++++++++++++-- web/src/cases/drill.test.ts | 7 +++++ 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/web/src/cases/algs.test.ts b/web/src/cases/algs.test.ts index af9a98c..b695d98 100644 --- a/web/src/cases/algs.test.ts +++ b/web/src/cases/algs.test.ts @@ -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)) @@ -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) + }) +}) diff --git a/web/src/cases/algs.ts b/web/src/cases/algs.ts index ac21ac9..bde1ccb 100644 --- a/web/src/cases/algs.ts +++ b/web/src/cases/algs.ts @@ -46,6 +46,43 @@ export const ALT_ALGS: Record = { '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'"], } /** ケースで選べる手順(先頭が標準手順)。 */ @@ -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) } /** 手順練習で選んだ手順を覚えておく。 */ diff --git a/web/src/cases/drill.test.ts b/web/src/cases/drill.test.ts index 296a439..a1b6638 100644 --- a/web/src/cases/drill.test.ts +++ b/web/src/cases/drill.test.ts @@ -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))