blog: Rubiks' Cube Scrambler post
Implement the rubiks-cube-scrambler custom element, including JS and template files. Put these things in the body-extras.html partial that the termlite theme added. resource-builders: Update submodule commit termlite: Update submodule commit
This commit is contained in:
parent
cb16a35020
commit
122e55b1fa
7 changed files with 221 additions and 2 deletions
123
assets/scripts/rubiks/scrambler.js
Normal file
123
assets/scripts/rubiks/scrambler.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
class RubiksCubeScrambler extends HTMLElement {
|
||||
static #RandomMoveHysteresisMaxLength = 2;
|
||||
|
||||
#shadowRoot;
|
||||
#movesListElement;
|
||||
|
||||
#numberOfMovesToGenerate = 25;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#shadowRoot = this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
scramble() {
|
||||
console.log("Randomizing Rubik's cube...");
|
||||
|
||||
const movesList = this.#movesListElement;
|
||||
|
||||
while (movesList.childElementCount > this.#numberOfMovesToGenerate) {
|
||||
movesList.removeChild(movesList.lastChild);
|
||||
}
|
||||
|
||||
let randomMoveHysteresis = [];
|
||||
|
||||
for (let i = 0; i < this.#numberOfMovesToGenerate; i++) {
|
||||
const randomMove = this.#randomMove(randomMoveHysteresis);
|
||||
|
||||
let moveItem;
|
||||
if (i < movesList.childElementCount) {
|
||||
moveItem = movesList.children[i];
|
||||
} else {
|
||||
moveItem = document.createElement("li");
|
||||
movesList.appendChild(moveItem);
|
||||
}
|
||||
|
||||
moveItem.classList.add("scrambler__move");
|
||||
moveItem.classList.remove("scrambler__move--start", "scrambler__move--end");
|
||||
|
||||
if (randomMove.includes("2")) {
|
||||
moveItem.classList.add("scrambler__move--start");
|
||||
} else if (randomMove.includes("'")) {
|
||||
moveItem.classList.add("scrambler__move--end");
|
||||
}
|
||||
|
||||
moveItem.innerText = randomMove;
|
||||
}
|
||||
}
|
||||
|
||||
#randomMove(hysteresis) {
|
||||
const faces = "FBLRUD";
|
||||
|
||||
let move;
|
||||
do {
|
||||
move = faces.charAt(Math.floor(Math.random() * faces.length));
|
||||
} while (hysteresis && hysteresis.includes(move));
|
||||
|
||||
if (hysteresis) {
|
||||
hysteresis.unshift(move);
|
||||
while (hysteresis.length > RubiksCubeScrambler.#RandomMoveHysteresisMaxLength) {
|
||||
hysteresis.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const modifierFactor = Math.random();
|
||||
if (modifierFactor < 0.33333) {
|
||||
move = "2" + move;
|
||||
} else if (modifierFactor < 0.666666) {
|
||||
move = move + "'";
|
||||
}
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
#removeAllMoves() {
|
||||
const element = this.#movesListElement;
|
||||
while (element.hasChildNodes()) {
|
||||
element.removeChild(element.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Custom Element
|
||||
|
||||
connectedCallback() {
|
||||
let template = document.getElementById("rubiks-cube-scrambler-template");
|
||||
console.assert(template, "Couldn't find RubiksCubeScrambler component template in the document");
|
||||
|
||||
const shadowRoot = this.#shadowRoot;
|
||||
shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
|
||||
this.#movesListElement = shadowRoot.querySelector(".scrambler__move-list");
|
||||
|
||||
shadowRoot
|
||||
.querySelector("button[name='scramble']")
|
||||
.addEventListener("click", () => this.scramble());
|
||||
|
||||
const patternLengthInputElement = shadowRoot.querySelector(".scrambler__pattern-length > input");
|
||||
patternLengthInputElement.value = this.#numberOfMovesToGenerate;
|
||||
patternLengthInputElement.addEventListener("input", event => {
|
||||
try {
|
||||
const integerValue = parseInt(event.target.value);
|
||||
this.#numberOfMovesToGenerate = integerValue;
|
||||
} catch (e) {
|
||||
console.error("Non-integer value of pattern length field", e);
|
||||
}
|
||||
});
|
||||
|
||||
this.scramble();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
console.debug("RubiksCubeScrambler attribute changed", name, oldValue, newValue);
|
||||
if (name === "count") {
|
||||
try {
|
||||
let newIntValue = parseInt(newValue);
|
||||
this.#numberOfMovesToGenerate = newIntValue;
|
||||
} catch (e) {
|
||||
console.error("`count` attribute should have an integer value.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("rubiks-cube-scrambler", RubiksCubeScrambler);
|
30
content/blog/2024/rubiks-scrambler.md
Normal file
30
content/blog/2024/rubiks-scrambler.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: Rubik's Cube Scrambler
|
||||
date: 2024-11-13T15:34:22-08:00
|
||||
tags:
|
||||
- Tech
|
||||
- Puzzles
|
||||
- Rubik's Cube
|
||||
- HTML
|
||||
- JavaScript
|
||||
- CSS
|
||||
- Web Components
|
||||
---
|
||||
|
||||
Here's a silly thing I made while I was home sick today. It's a widget that
|
||||
produces a randomized pattern of [moves][rmoves] to scramble a 3×3 [Rubik's
|
||||
Cube][rcube].
|
||||
|
||||
<figure class="figure--main-column figure--object">
|
||||
{{< rubiks/scrambler >}}
|
||||
</figure>
|
||||
|
||||
This thing is a [Web Component][wc]. The interactive logic lives inside a custom
|
||||
[HTMLElement][htmlelement], and the content and styling are specified inside a
|
||||
[`<template>`][template] element.
|
||||
|
||||
[rcube]: https://www.rubiks.com
|
||||
[rmoves]: https://jperm.net/3x3/moves
|
||||
[wc]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components
|
||||
[htmlelement]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
||||
[template]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
|
4
layouts/partials/base/body-extras.html
Normal file
4
layouts/partials/base/body-extras.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
{{ if .HasShortcode "rubiks/scrambler" }}
|
||||
{{ partial "rubiks/scrambler-template.html" . }}
|
||||
{{ partial "resource_builders/script.html" (dict "resource" "scripts/rubiks/scrambler.js") }}
|
||||
{{ end }}
|
61
layouts/partials/rubiks/scrambler-template.html
Normal file
61
layouts/partials/rubiks/scrambler-template.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template id="rubiks-cube-scrambler-template">
|
||||
<ol class="scrambler__move-list"></ol>
|
||||
<div class="scrambler__pattern-length">
|
||||
<label for="pattern-length" class="scrambler__label">Length</label>
|
||||
<input type="text" name="pattern-length">
|
||||
</div>
|
||||
<div class="scrambler__scramble">
|
||||
<button name="scramble" class="scrambler__button">Scramble!</button>
|
||||
</div>
|
||||
<style>
|
||||
.scrambler__move-list {
|
||||
display: grid;
|
||||
font-family: var(--font-family-monospace);
|
||||
gap: var(--space-xxs);
|
||||
grid-template-columns: repeat(7, 3ch);
|
||||
list-style: none;
|
||||
margin-block: 0;
|
||||
padding-inline: 0;
|
||||
|
||||
.scrambler__move {
|
||||
justify-self: center;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.scrambler__move--start {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.scrambler__move--end {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
.scrambler__scramble,
|
||||
.scrambler__pattern-length
|
||||
{
|
||||
font-size: var(--text-s);
|
||||
margin-block-start: var(--space-s);
|
||||
}
|
||||
|
||||
.scrambler__button {
|
||||
font-size: var(--text-s);
|
||||
}
|
||||
|
||||
.scrambler__label {
|
||||
color: var(--text-color-secondary);
|
||||
font-family: var(--font-family-monospace);
|
||||
text-transform: uppercase;
|
||||
|
||||
&::after {
|
||||
content: " = ";
|
||||
}
|
||||
}
|
||||
|
||||
input[name="pattern-length"] {
|
||||
font-size: var(--text-s);
|
||||
text-align: end;
|
||||
width: 4ch;
|
||||
}
|
||||
</style>
|
||||
</template>
|
1
layouts/shortcodes/rubiks/scrambler.html
Normal file
1
layouts/shortcodes/rubiks/scrambler.html
Normal file
|
@ -0,0 +1 @@
|
|||
<rubiks-cube-scrambler></rubiks-cube-scrambler>
|
|
@ -1 +1 @@
|
|||
Subproject commit b9aa2d4201d986b841ff8abf97eb72b5492fa8de
|
||||
Subproject commit 73e3cec7117f3a061554f7a9799285a97a284a22
|
|
@ -1 +1 @@
|
|||
Subproject commit ac168d7143d0437a837549a6cf96902e30409dac
|
||||
Subproject commit c4821daa6e080cc16f339884d022c243b163c0dc
|
Loading…
Add table
Add a link
Reference in a new issue