Spaces:
Running
Running
Randomize demo cube scrambles
Browse files- app.js +28 -5
- index.html +2 -2
app.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
| 2 |
|
| 3 |
const FACES = ["U", "R", "F", "D", "L", "B"];
|
| 4 |
const FACE_NAMES = {U: "Top", R: "Right", F: "Front", D: "Bottom", L: "Left", B: "Back"};
|
|
|
|
|
|
|
|
|
|
| 5 |
const COLOR_PRESETS = [
|
| 6 |
{name: "white", label: "White", hex: "#f4f4f5"},
|
| 7 |
{name: "yellow", label: "Yellow", hex: "#facc15"},
|
|
@@ -103,6 +106,26 @@ const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, millise
|
|
| 103 |
const nextPaint = () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
| 104 |
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
async function transitionTransform(element, transform, duration, easing = "cubic-bezier(.4,.01,.18,1)") {
|
| 107 |
element.style.transition = `transform ${Math.round(duration)}ms ${easing}`;
|
| 108 |
await nextPaint();
|
|
@@ -764,17 +787,17 @@ function loadSolvedCube() {
|
|
| 764 |
chooseView("HOME");
|
| 765 |
}
|
| 766 |
|
| 767 |
-
function
|
| 768 |
hideEntryMessage();
|
| 769 |
try {
|
| 770 |
-
const scramble =
|
| 771 |
let canonical = FACES.map(face => face.repeat(9)).join("");
|
| 772 |
scramble.forEach(move => canonical = applyCanonicalMove(canonical, move));
|
| 773 |
replaceCubeState(coloredFacesFromCanonical(canonical, SOLVED_COLORS));
|
| 774 |
-
showEntryMessage(`
|
| 775 |
chooseView("HOME");
|
| 776 |
} catch (error) {
|
| 777 |
-
showEntryMessage(error.message || "
|
| 778 |
}
|
| 779 |
}
|
| 780 |
|
|
@@ -1056,7 +1079,7 @@ function initialize() {
|
|
| 1056 |
initializeCameraControls();
|
| 1057 |
$("clearBtn").addEventListener("click", clearCube);
|
| 1058 |
$("solvedBtn").addEventListener("click", loadSolvedCube);
|
| 1059 |
-
$("demoBtn").addEventListener("click",
|
| 1060 |
$("solveBtn").addEventListener("click", solveCube);
|
| 1061 |
$("prevBtn").addEventListener("click", () => { stopPlayback(); stepSolution(-1); });
|
| 1062 |
$("nextBtn").addEventListener("click", () => { stopPlayback(); stepSolution(1); });
|
|
|
|
| 2 |
|
| 3 |
const FACES = ["U", "R", "F", "D", "L", "B"];
|
| 4 |
const FACE_NAMES = {U: "Top", R: "Right", F: "Front", D: "Bottom", L: "Left", B: "Back"};
|
| 5 |
+
const FACE_AXES = {U: "Y", D: "Y", R: "X", L: "X", F: "Z", B: "Z"};
|
| 6 |
+
const SCRAMBLE_SUFFIXES = ["", "'", "2"];
|
| 7 |
+
const DEMO_SCRAMBLE_LENGTH = 20;
|
| 8 |
const COLOR_PRESETS = [
|
| 9 |
{name: "white", label: "White", hex: "#f4f4f5"},
|
| 10 |
{name: "yellow", label: "Yellow", hex: "#facc15"},
|
|
|
|
| 106 |
const nextPaint = () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
| 107 |
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
| 108 |
|
| 109 |
+
function randomInteger(maximum) {
|
| 110 |
+
const values = new Uint32Array(1);
|
| 111 |
+
const limit = 0x100000000 - (0x100000000 % maximum);
|
| 112 |
+
do window.crypto.getRandomValues(values); while (values[0] >= limit);
|
| 113 |
+
return values[0] % maximum;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
function randomScramble(length = DEMO_SCRAMBLE_LENGTH) {
|
| 117 |
+
const scramble = [];
|
| 118 |
+
let previousAxis = null;
|
| 119 |
+
for (let index = 0; index < length; index++) {
|
| 120 |
+
const availableFaces = FACES.filter(face => FACE_AXES[face] !== previousAxis);
|
| 121 |
+
const face = availableFaces[randomInteger(availableFaces.length)];
|
| 122 |
+
const suffix = SCRAMBLE_SUFFIXES[randomInteger(SCRAMBLE_SUFFIXES.length)];
|
| 123 |
+
scramble.push(`${face}${suffix}`);
|
| 124 |
+
previousAxis = FACE_AXES[face];
|
| 125 |
+
}
|
| 126 |
+
return scramble;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
async function transitionTransform(element, transform, duration, easing = "cubic-bezier(.4,.01,.18,1)") {
|
| 130 |
element.style.transition = `transform ${Math.round(duration)}ms ${easing}`;
|
| 131 |
await nextPaint();
|
|
|
|
| 787 |
chooseView("HOME");
|
| 788 |
}
|
| 789 |
|
| 790 |
+
function loadRandomDemoCube() {
|
| 791 |
hideEntryMessage();
|
| 792 |
try {
|
| 793 |
+
const scramble = randomScramble();
|
| 794 |
let canonical = FACES.map(face => face.repeat(9)).join("");
|
| 795 |
scramble.forEach(move => canonical = applyCanonicalMove(canonical, move));
|
| 796 |
replaceCubeState(coloredFacesFromCanonical(canonical, SOLVED_COLORS));
|
| 797 |
+
showEntryMessage(`Random demo loaded: ${scramble.join(" ")}`, "success");
|
| 798 |
chooseView("HOME");
|
| 799 |
} catch (error) {
|
| 800 |
+
showEntryMessage(error.message || "A random demo cube could not be loaded.");
|
| 801 |
}
|
| 802 |
}
|
| 803 |
|
|
|
|
| 1079 |
initializeCameraControls();
|
| 1080 |
$("clearBtn").addEventListener("click", clearCube);
|
| 1081 |
$("solvedBtn").addEventListener("click", loadSolvedCube);
|
| 1082 |
+
$("demoBtn").addEventListener("click", loadRandomDemoCube);
|
| 1083 |
$("solveBtn").addEventListener("click", solveCube);
|
| 1084 |
$("prevBtn").addEventListener("click", () => { stopPlayback(); stepSolution(-1); });
|
| 1085 |
$("nextBtn").addEventListener("click", () => { stopPlayback(); stepSolution(1); });
|
index.html
CHANGED
|
@@ -103,7 +103,7 @@
|
|
| 103 |
<div class="preset-row">
|
| 104 |
<button class="soft-btn" id="clearBtn">Clear</button>
|
| 105 |
<button class="soft-btn" id="solvedBtn">Solved example</button>
|
| 106 |
-
<button class="soft-btn accent" id="demoBtn">
|
| 107 |
</div>
|
| 108 |
<button class="solve-btn" id="solveBtn" disabled><span>Solve my cube</span><i>→</i></button>
|
| 109 |
<div class="status-message" id="entryMessage" role="status" aria-live="polite"></div>
|
|
@@ -147,6 +147,6 @@
|
|
| 147 |
<button class="remove-color" id="removeColor">Clear this sticker</button>
|
| 148 |
</div>
|
| 149 |
|
| 150 |
-
<script src="./app.js?v=20260810-
|
| 151 |
</body>
|
| 152 |
</html>
|
|
|
|
| 103 |
<div class="preset-row">
|
| 104 |
<button class="soft-btn" id="clearBtn">Clear</button>
|
| 105 |
<button class="soft-btn" id="solvedBtn">Solved example</button>
|
| 106 |
+
<button class="soft-btn accent" id="demoBtn">Random demo</button>
|
| 107 |
</div>
|
| 108 |
<button class="solve-btn" id="solveBtn" disabled><span>Solve my cube</span><i>→</i></button>
|
| 109 |
<div class="status-message" id="entryMessage" role="status" aria-live="polite"></div>
|
|
|
|
| 147 |
<button class="remove-color" id="removeColor">Clear this sticker</button>
|
| 148 |
</div>
|
| 149 |
|
| 150 |
+
<script src="./app.js?v=20260810-random-demo-v1" defer></script>
|
| 151 |
</body>
|
| 152 |
</html>
|