Building a Complete Rubik's Cube Trainer in One AI Session

5 min read

The idea

I wanted a site that teaches Rubik’s cube algorithms interactively. Not videos, not static images, not dry notation in a table. Real 3D animations where you can see each move, pause, rewind, and compare variants side by side.

The CFOP method has 119 cases across three stages: F2L (41 cases), OLL (57 cases), and PLL (21 cases). Each case has 2-3 algorithm variants. That’s 240 algorithms total that need to be correct — one notation error and the cube doesn’t solve.

The bet: build the entire project in a single session with Claude Code, from npm create astro to having all 4 roadmap phases completed.

The stack

The most important architecture decision was using Astro with islands architecture. An algorithm reference site is mostly static content — the case grids are pure HTML. You only need JavaScript for two things: the 3D viewer (cubing.js weighs ~1MB) and search/filter interactivity.

// TwistyPlayer loads ONLY on detail pages
// Grid pages load zero bytes of cubing.js
<TwistyPlayer client:only="react" algorithm={alg.notation} setup={caseData.setup} />

The client:only="react" directive is key. cubing.js uses browser APIs that break SSR, so we tell Astro not to even attempt server-side rendering for this component.

The full stack:

  • Astro 6 — generates 124 static pages in ~1.8 seconds
  • React 19 — only for interactive islands
  • Tailwind CSS v4 — dark theme, responsive
  • TypeScript strict — no any types, typed interfaces for the entire data model
  • cubing.js — 3D rendering and algorithm validation

Validation as a safety net

This is the most critical and least visible feature. Before any algorithm reaches the site, a script validates it mathematically:

scripts/validate-algorithms.ts
const kpuzzle = await cube3x3x3.kpuzzle();
const solvedState = kpuzzle.defaultPattern();
// For each algorithm: apply setup, then algorithm, verify solved
const scrambled = solvedState.applyAlg(new Alg(setup));
const result = scrambled.applyAlg(new Alg(notation));
const isValid = result.isIdentical(solvedState);

npm run validate runs this against all 240 algorithms. If any fail, the build breaks. This gave me the confidence to generate data aggressively — if the math passes, the algorithm is correct.

Result: 240/240 algorithms validated across all three stages.

4 phases in one session

The project was organized into a 4-phase roadmap with 23 tasks. Here’s how execution went:

Phase 1 — Foundation (8 tasks): Project setup, cubing.js integration, data model, 21 PLL cases with 44 algorithms, validation script, grid and detail pages.

Phase 2 — Full Algorithm Set (6 tasks): 57 OLL cases (114 algorithms), 41 F2L cases (82 algorithms), pages for all three stages, search/filter with collapsible groups.

Phase 3 — Polish (5 tasks): Mobile responsive design, prev/next navigation, side-by-side algorithm comparison, SEO (Open Graph, sitemap, JSON-LD), performance with chunk splitting.

Phase 4 — Growth (5 tasks): Practice mode with random scrambles and timer, progress tracking in localStorage, contribution guide, scaffolds for ads and donations.

25 commits. 124 pages. All in one session.

Interesting technical decisions

Manual chunk splitting

cubing.js is a large library (~967KB). Without configuration, Vite fragments it into 17 chunks that load unpredictably. With manual chunks:

astro.config.mjs
vite: {
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules/cubing')) return 'cubing';
if (id.includes('node_modules/react')) return 'react-vendor';
},
},
},
},
}

Result: grid pages load ~9KB of JS. Detail pages load a single cubing.js chunk that the browser caches.

Data as static JSON

No database, no CMS, no API. All 240 algorithms live in three JSON files that Astro imports at build time:

// In any Astro page
import pllData from '../data/pll.json';
import type { StageData } from '../data/types';
const data = pllData as StageData;

This makes the build deterministic and fast. If an algorithm is wrong, fix the JSON, run npm run validate, rebuild.

Practice mode with recognition timer

Practice mode shows a random case with its scramble. The user applies the scramble on a physical cube, tries to recognize the case, and clicks “Reveal” to see the algorithm. The time between the case appearing and the click is measured as “recognition time.”

const pickRandomCase = useCallback(() => {
const cases = stages[stage];
const randomIndex = Math.floor(Math.random() * cases.length);
setCurrentCase(cases[randomIndex]);
setRevealed(false);
setStartTime(Date.now());
}, [stage, stages]);

Simple but effective for pattern recognition practice.

What’s still pending

The project is functional but some manual work remains:

  • Alternate algorithms: Many second algorithms in OLL and F2L are copies of the default. They need real alternatives curated by a cuber.
  • Deploy: The build works, just needs to be connected to Vercel.
  • AdSense and donations: Scaffolds are ready, just need real accounts.
  • Visual QA: Verify everything looks good on actual devices.

Where do the algorithms come from

I’m handing this one over to Claude. Let it explain how the data side worked.


What follows is written by me, Claude. Luis asked me to be transparent about this part.

All 240 algorithms came from my training data. No site was scraped at runtime. CFOP algorithms are extensively documented across the speedcubing community — algdb.net, jperm.net, SpeedSolving wiki, cubeskills.com — and that information is in my training data. I “know” standard PLL, OLL, and F2L algorithms the same way I know TypeScript syntax.

The real safety net is the mathematical validation. For each of the 240 algorithms, the cubing.js script takes the solved cube state, applies the scramble, applies the algorithm, and verifies the result is identical to the solved state. If the math passes, the algorithm solves the case. All 240 passed.

But there are limitations worth being honest about:

  • The defaults are solid. These are community standards — Sune, Anti-Sune, T Perm, J Perm. I know them well from my training data.
  • The alternates are the weak spot. For about 51 OLL cases and several F2L cases, the second algorithm is a copy of the default. I couldn’t generate distinct alternatives that passed validation. They’re marked as placeholders.
  • Mathematical validation is not ergonomic validation. The script verifies the algorithm solves the case, but it doesn’t verify that it’s comfortable to execute, finger-trick friendly, or the one the community actually prefers to teach.

That’s why human review is still necessary. The math guarantees they solve the cube. A cuber guarantees they’re the right ones to teach.

Handing it back to Luis.


Takeaways

  1. Automated validation enables speed. Without the validation script, every algorithm would need manual review. With it, you can generate data aggressively and trust the math.

  2. Astro islands are perfect for content sites with focused interactivity. 9KB of JS on grids vs 967KB on detail pages — you can’t achieve that gap with a SPA.

  3. A well-typed data model scales. The same TypeScript interfaces power validation, rendering, filtering, and practice mode. A type change propagates everywhere.

  4. Static JSON > database for bounded datasets. 240 algorithms in 3 JSON files. Build time: 1.8 seconds. No server, no cold starts, no costs.

The repo is ready for contributions. If you’re a cuber and want to improve the alternate algorithms, CONTRIBUTING.md has everything you need to get started.